github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/builder/azure/arm/config.go (about)

     1  package arm
     2  
     3  import (
     4  	"crypto/rand"
     5  	"crypto/rsa"
     6  	"crypto/x509"
     7  	"crypto/x509/pkix"
     8  	"encoding/base64"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"math/big"
    13  	"regexp"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/Azure/azure-sdk-for-go/arm/compute"
    18  	"github.com/Azure/go-autorest/autorest/azure"
    19  	"github.com/Azure/go-autorest/autorest/to"
    20  	"github.com/masterzen/winrm"
    21  
    22  	"github.com/hashicorp/packer/builder/azure/common/constants"
    23  	"github.com/hashicorp/packer/builder/azure/pkcs12"
    24  	"github.com/hashicorp/packer/common"
    25  	"github.com/hashicorp/packer/helper/communicator"
    26  	"github.com/hashicorp/packer/helper/config"
    27  	"github.com/hashicorp/packer/packer"
    28  	"github.com/hashicorp/packer/template/interpolate"
    29  
    30  	"golang.org/x/crypto/ssh"
    31  )
    32  
    33  const (
    34  	DefaultCloudEnvironmentName              = "Public"
    35  	DefaultImageVersion                      = "latest"
    36  	DefaultUserName                          = "packer"
    37  	DefaultPrivateVirtualNetworkWithPublicIp = false
    38  	DefaultVMSize                            = "Standard_A1"
    39  )
    40  
    41  var (
    42  	reCaptureContainerName = regexp.MustCompile("^[a-z0-9][a-z0-9\\-]{2,62}$")
    43  	reCaptureNamePrefix    = regexp.MustCompile("^[A-Za-z0-9][A-Za-z0-9_\\-\\.]{0,23}$")
    44  )
    45  
    46  type Config struct {
    47  	common.PackerConfig `mapstructure:",squash"`
    48  
    49  	// Authentication via OAUTH
    50  	ClientID       string `mapstructure:"client_id"`
    51  	ClientSecret   string `mapstructure:"client_secret"`
    52  	ObjectID       string `mapstructure:"object_id"`
    53  	TenantID       string `mapstructure:"tenant_id"`
    54  	SubscriptionID string `mapstructure:"subscription_id"`
    55  
    56  	// Capture
    57  	CaptureNamePrefix    string `mapstructure:"capture_name_prefix"`
    58  	CaptureContainerName string `mapstructure:"capture_container_name"`
    59  
    60  	// Compute
    61  	ImagePublisher string `mapstructure:"image_publisher"`
    62  	ImageOffer     string `mapstructure:"image_offer"`
    63  	ImageSku       string `mapstructure:"image_sku"`
    64  	ImageVersion   string `mapstructure:"image_version"`
    65  	ImageUrl       string `mapstructure:"image_url"`
    66  
    67  	CustomManagedImageResourceGroupName string `mapstructure:"custom_managed_image_resource_group_name"`
    68  	CustomManagedImageName              string `mapstructure:"custom_managed_image_name"`
    69  	customManagedImageID                string
    70  
    71  	Location string `mapstructure:"location"`
    72  	VMSize   string `mapstructure:"vm_size"`
    73  
    74  	ManagedImageResourceGroupName  string `mapstructure:"managed_image_resource_group_name"`
    75  	ManagedImageName               string `mapstructure:"managed_image_name"`
    76  	ManagedImageStorageAccountType string `mapstructure:"managed_image_storage_account_type"`
    77  	managedImageStorageAccountType compute.StorageAccountTypes
    78  	manageImageLocation            string
    79  
    80  	// Deployment
    81  	AzureTags                         map[string]*string `mapstructure:"azure_tags"`
    82  	ResourceGroupName                 string             `mapstructure:"resource_group_name"`
    83  	StorageAccount                    string             `mapstructure:"storage_account"`
    84  	TempComputeName                   string             `mapstructure:"temp_compute_name"`
    85  	TempResourceGroupName             string             `mapstructure:"temp_resource_group_name"`
    86  	storageAccountBlobEndpoint        string
    87  	CloudEnvironmentName              string `mapstructure:"cloud_environment_name"`
    88  	cloudEnvironment                  *azure.Environment
    89  	PrivateVirtualNetworkWithPublicIp bool   `mapstructure:"private_virtual_network_with_public_ip"`
    90  	VirtualNetworkName                string `mapstructure:"virtual_network_name"`
    91  	VirtualNetworkSubnetName          string `mapstructure:"virtual_network_subnet_name"`
    92  	VirtualNetworkResourceGroupName   string `mapstructure:"virtual_network_resource_group_name"`
    93  	CustomDataFile                    string `mapstructure:"custom_data_file"`
    94  	customData                        string
    95  
    96  	// OS
    97  	OSType       string `mapstructure:"os_type"`
    98  	OSDiskSizeGB int32  `mapstructure:"os_disk_size_gb"`
    99  
   100  	// Runtime Values
   101  	UserName               string
   102  	Password               string
   103  	tmpAdminPassword       string
   104  	tmpCertificatePassword string
   105  	tmpResourceGroupName   string
   106  	tmpComputeName         string
   107  	tmpDeploymentName      string
   108  	tmpKeyVaultName        string
   109  	tmpOSDiskName          string
   110  	tmpWinRMCertificateUrl string
   111  
   112  	useDeviceLogin bool
   113  
   114  	// Authentication with the VM via SSH
   115  	sshAuthorizedKey string
   116  	sshPrivateKey    string
   117  
   118  	// Authentication with the VM via WinRM
   119  	winrmCertificate string
   120  
   121  	Comm communicator.Config `mapstructure:",squash"`
   122  	ctx  *interpolate.Context
   123  }
   124  
   125  type keyVaultCertificate struct {
   126  	Data     string `json:"data"`
   127  	DataType string `json:"dataType"`
   128  	Password string `json:"password,omitempty"`
   129  }
   130  
   131  func (c *Config) toVMID() string {
   132  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s", c.SubscriptionID, c.tmpResourceGroupName, c.tmpComputeName)
   133  }
   134  
   135  func (c *Config) isManagedImage() bool {
   136  	return c.ManagedImageName != ""
   137  }
   138  
   139  func (c *Config) toVirtualMachineCaptureParameters() *compute.VirtualMachineCaptureParameters {
   140  	return &compute.VirtualMachineCaptureParameters{
   141  		DestinationContainerName: &c.CaptureContainerName,
   142  		VhdPrefix:                &c.CaptureNamePrefix,
   143  		OverwriteVhds:            to.BoolPtr(false),
   144  	}
   145  }
   146  
   147  func (c *Config) toImageParameters() *compute.Image {
   148  	return &compute.Image{
   149  		ImageProperties: &compute.ImageProperties{
   150  			SourceVirtualMachine: &compute.SubResource{
   151  				ID: to.StringPtr(c.toVMID()),
   152  			},
   153  		},
   154  		Location: to.StringPtr(c.Location),
   155  		Tags:     &c.AzureTags,
   156  	}
   157  }
   158  
   159  func (c *Config) createCertificate() (string, error) {
   160  	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
   161  	if err != nil {
   162  		err = fmt.Errorf("Failed to Generate Private Key: %s", err)
   163  		return "", err
   164  	}
   165  
   166  	host := fmt.Sprintf("%s.cloudapp.net", c.tmpComputeName)
   167  	notBefore := time.Now()
   168  	notAfter := notBefore.Add(24 * time.Hour)
   169  
   170  	serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
   171  	if err != nil {
   172  		err = fmt.Errorf("Failed to Generate Serial Number: %v", err)
   173  		return "", err
   174  	}
   175  
   176  	template := x509.Certificate{
   177  		SerialNumber: serialNumber,
   178  		Issuer: pkix.Name{
   179  			CommonName: host,
   180  		},
   181  		Subject: pkix.Name{
   182  			CommonName: host,
   183  		},
   184  		NotBefore: notBefore,
   185  		NotAfter:  notAfter,
   186  
   187  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
   188  		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
   189  		BasicConstraintsValid: true,
   190  	}
   191  
   192  	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
   193  	if err != nil {
   194  		err = fmt.Errorf("Failed to Create Certificate: %s", err)
   195  		return "", err
   196  	}
   197  
   198  	pfxBytes, err := pkcs12.Encode(derBytes, privateKey, c.tmpCertificatePassword)
   199  	if err != nil {
   200  		err = fmt.Errorf("Failed to encode certificate as PFX: %s", err)
   201  		return "", err
   202  	}
   203  
   204  	keyVaultDescription := keyVaultCertificate{
   205  		Data:     base64.StdEncoding.EncodeToString(pfxBytes),
   206  		DataType: "pfx",
   207  		Password: c.tmpCertificatePassword,
   208  	}
   209  
   210  	bytes, err := json.Marshal(keyVaultDescription)
   211  	if err != nil {
   212  		err = fmt.Errorf("Failed to marshal key vault description: %s", err)
   213  		return "", err
   214  	}
   215  
   216  	return base64.StdEncoding.EncodeToString(bytes), nil
   217  }
   218  
   219  func newConfig(raws ...interface{}) (*Config, []string, error) {
   220  	var c Config
   221  
   222  	err := config.Decode(&c, &config.DecodeOpts{
   223  		Interpolate:        true,
   224  		InterpolateContext: c.ctx,
   225  	}, raws...)
   226  
   227  	if err != nil {
   228  		return nil, nil, err
   229  	}
   230  
   231  	provideDefaultValues(&c)
   232  	setRuntimeValues(&c)
   233  	setUserNamePassword(&c)
   234  	err = setCloudEnvironment(&c)
   235  	if err != nil {
   236  		return nil, nil, err
   237  	}
   238  
   239  	err = setCustomData(&c)
   240  	if err != nil {
   241  		return nil, nil, err
   242  	}
   243  
   244  	// NOTE: if the user did not specify a communicator, then default to both
   245  	// SSH and WinRM.  This is for backwards compatibility because the code did
   246  	// not specifically force the user to set a communicator.
   247  	if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "ssh") {
   248  		err = setSshValues(&c)
   249  		if err != nil {
   250  			return nil, nil, err
   251  		}
   252  	}
   253  
   254  	if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "winrm") {
   255  		err = setWinRMCertificate(&c)
   256  		if err != nil {
   257  			return nil, nil, err
   258  		}
   259  	}
   260  
   261  	var errs *packer.MultiError
   262  	errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(c.ctx)...)
   263  
   264  	assertRequiredParametersSet(&c, errs)
   265  	assertTagProperties(&c, errs)
   266  	if errs != nil && len(errs.Errors) > 0 {
   267  		return nil, nil, errs
   268  	}
   269  
   270  	return &c, nil, nil
   271  }
   272  
   273  func setSshValues(c *Config) error {
   274  	if c.Comm.SSHTimeout == 0 {
   275  		c.Comm.SSHTimeout = 20 * time.Minute
   276  	}
   277  
   278  	if c.Comm.SSHPrivateKey != "" {
   279  		privateKeyBytes, err := ioutil.ReadFile(c.Comm.SSHPrivateKey)
   280  		if err != nil {
   281  			return err
   282  		}
   283  		signer, err := ssh.ParsePrivateKey(privateKeyBytes)
   284  		if err != nil {
   285  			return err
   286  		}
   287  
   288  		publicKey := signer.PublicKey()
   289  		c.sshAuthorizedKey = fmt.Sprintf("%s %s packer Azure Deployment%s",
   290  			publicKey.Type(),
   291  			base64.StdEncoding.EncodeToString(publicKey.Marshal()),
   292  			time.Now().Format(time.RFC3339))
   293  		c.sshPrivateKey = string(privateKeyBytes)
   294  
   295  	} else {
   296  		sshKeyPair, err := NewOpenSshKeyPair()
   297  		if err != nil {
   298  			return err
   299  		}
   300  
   301  		c.sshAuthorizedKey = sshKeyPair.AuthorizedKey()
   302  		c.sshPrivateKey = sshKeyPair.PrivateKey()
   303  	}
   304  
   305  	return nil
   306  }
   307  
   308  func setWinRMCertificate(c *Config) error {
   309  	c.Comm.WinRMTransportDecorator =
   310  		func() winrm.Transporter {
   311  			return &winrm.ClientNTLM{}
   312  		}
   313  
   314  	cert, err := c.createCertificate()
   315  	c.winrmCertificate = cert
   316  
   317  	return err
   318  }
   319  
   320  func setRuntimeValues(c *Config) {
   321  	var tempName = NewTempName()
   322  
   323  	c.tmpAdminPassword = tempName.AdminPassword
   324  	c.tmpCertificatePassword = tempName.CertificatePassword
   325  	if c.TempComputeName == "" {
   326  		c.tmpComputeName = tempName.ComputeName
   327  	} else {
   328  		c.tmpComputeName = c.TempComputeName
   329  	}
   330  	c.tmpDeploymentName = tempName.DeploymentName
   331  	if c.TempResourceGroupName == "" {
   332  		c.tmpResourceGroupName = tempName.ResourceGroupName
   333  	} else {
   334  		c.tmpResourceGroupName = c.TempResourceGroupName
   335  	}
   336  	c.tmpOSDiskName = tempName.OSDiskName
   337  	c.tmpKeyVaultName = tempName.KeyVaultName
   338  }
   339  
   340  func setUserNamePassword(c *Config) {
   341  	if c.Comm.SSHUsername == "" {
   342  		c.Comm.SSHUsername = DefaultUserName
   343  	}
   344  
   345  	c.UserName = c.Comm.SSHUsername
   346  
   347  	if c.Comm.SSHPassword != "" {
   348  		c.Password = c.Comm.SSHPassword
   349  	} else {
   350  		c.Password = c.tmpAdminPassword
   351  	}
   352  }
   353  
   354  func setCloudEnvironment(c *Config) error {
   355  	lookup := map[string]string{
   356  		"CHINA":           "AzureChinaCloud",
   357  		"CHINACLOUD":      "AzureChinaCloud",
   358  		"AZURECHINACLOUD": "AzureChinaCloud",
   359  
   360  		"GERMAN":           "AzureGermanCloud",
   361  		"GERMANCLOUD":      "AzureGermanCloud",
   362  		"AZUREGERMANCLOUD": "AzureGermanCloud",
   363  
   364  		"GERMANY":           "AzureGermanCloud",
   365  		"GERMANYCLOUD":      "AzureGermanCloud",
   366  		"AZUREGERMANYCLOUD": "AzureGermanCloud",
   367  
   368  		"PUBLIC":           "AzurePublicCloud",
   369  		"PUBLICCLOUD":      "AzurePublicCloud",
   370  		"AZUREPUBLICCLOUD": "AzurePublicCloud",
   371  
   372  		"USGOVERNMENT":           "AzureUSGovernmentCloud",
   373  		"USGOVERNMENTCLOUD":      "AzureUSGovernmentCloud",
   374  		"AZUREUSGOVERNMENTCLOUD": "AzureUSGovernmentCloud",
   375  	}
   376  
   377  	name := strings.ToUpper(c.CloudEnvironmentName)
   378  	envName, ok := lookup[name]
   379  	if !ok {
   380  		return fmt.Errorf("There is no cloud envionment matching the name '%s'!", c.CloudEnvironmentName)
   381  	}
   382  
   383  	env, err := azure.EnvironmentFromName(envName)
   384  	c.cloudEnvironment = &env
   385  	return err
   386  }
   387  
   388  func setCustomData(c *Config) error {
   389  	if c.CustomDataFile == "" {
   390  		return nil
   391  	}
   392  
   393  	b, err := ioutil.ReadFile(c.CustomDataFile)
   394  	if err != nil {
   395  		return err
   396  	}
   397  
   398  	c.customData = base64.StdEncoding.EncodeToString(b)
   399  	return nil
   400  }
   401  
   402  func provideDefaultValues(c *Config) {
   403  	if c.VMSize == "" {
   404  		c.VMSize = DefaultVMSize
   405  	}
   406  
   407  	if c.ManagedImageStorageAccountType == "" {
   408  		c.managedImageStorageAccountType = compute.StandardLRS
   409  	}
   410  
   411  	if c.ImagePublisher != "" && c.ImageVersion == "" {
   412  		c.ImageVersion = DefaultImageVersion
   413  	}
   414  
   415  	if c.CloudEnvironmentName == "" {
   416  		c.CloudEnvironmentName = DefaultCloudEnvironmentName
   417  	}
   418  }
   419  
   420  func assertTagProperties(c *Config, errs *packer.MultiError) {
   421  	if len(c.AzureTags) > 15 {
   422  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("a max of 15 tags are supported, but %d were provided", len(c.AzureTags)))
   423  	}
   424  
   425  	for k, v := range c.AzureTags {
   426  		if len(k) > 512 {
   427  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 512 character limit", k, len(k)))
   428  		}
   429  		if len(*v) > 256 {
   430  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", v, len(*v)))
   431  		}
   432  	}
   433  }
   434  
   435  func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
   436  	/////////////////////////////////////////////
   437  	// Authentication via OAUTH
   438  
   439  	// Check if device login is being asked for, and is allowed.
   440  	//
   441  	// Device login is enabled if the user only defines SubscriptionID and not
   442  	// ClientID, ClientSecret, and TenantID.
   443  	//
   444  	// Device login is not enabled for Windows because the WinRM certificate is
   445  	// readable by the ObjectID of the App.  There may be another way to handle
   446  	// this case, but I am not currently aware of it - send feedback.
   447  	isUseDeviceLogin := func(c *Config) bool {
   448  		if c.OSType == constants.Target_Windows {
   449  			return false
   450  		}
   451  
   452  		return c.SubscriptionID != "" &&
   453  			c.ClientID == "" &&
   454  			c.ClientSecret == "" &&
   455  			c.TenantID == ""
   456  	}
   457  
   458  	if isUseDeviceLogin(c) {
   459  		c.useDeviceLogin = true
   460  	} else {
   461  		if c.ClientID == "" {
   462  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_id must be specified"))
   463  		}
   464  
   465  		if c.ClientSecret == "" {
   466  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_secret must be specified"))
   467  		}
   468  
   469  		if c.SubscriptionID == "" {
   470  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("A subscription_id must be specified"))
   471  		}
   472  	}
   473  
   474  	/////////////////////////////////////////////
   475  	// Capture
   476  	if c.CaptureContainerName == "" && c.ManagedImageName == "" {
   477  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name or managed_image_name must be specified"))
   478  	}
   479  
   480  	if c.CaptureNamePrefix == "" && c.ManagedImageResourceGroupName == "" {
   481  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix or managed_image_resource_group_name must be specified"))
   482  	}
   483  
   484  	if (c.CaptureNamePrefix != "" || c.CaptureContainerName != "") && (c.ManagedImageResourceGroupName != "" || c.ManagedImageName != "") {
   485  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("Either a VHD or a managed image can be built, but not both. Please specify either capture_container_name and capture_name_prefix or managed_image_resource_group_name and managed_image_name."))
   486  	}
   487  
   488  	if c.CaptureContainerName != "" {
   489  		if !reCaptureContainerName.MatchString(c.CaptureContainerName) {
   490  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must satisfy the regular expression %q.", reCaptureContainerName.String()))
   491  		}
   492  
   493  		if strings.HasSuffix(c.CaptureContainerName, "-") {
   494  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not end with a hyphen, e.g. '-'."))
   495  		}
   496  
   497  		if strings.Contains(c.CaptureContainerName, "--") {
   498  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not contain consecutive hyphens, e.g. '--'."))
   499  		}
   500  
   501  		if c.CaptureNamePrefix == "" {
   502  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must be specified"))
   503  		}
   504  
   505  		if !reCaptureNamePrefix.MatchString(c.CaptureNamePrefix) {
   506  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must satisfy the regular expression %q.", reCaptureNamePrefix.String()))
   507  		}
   508  
   509  		if strings.HasSuffix(c.CaptureNamePrefix, "-") || strings.HasSuffix(c.CaptureNamePrefix, ".") {
   510  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must not end with a hyphen or period."))
   511  		}
   512  	}
   513  
   514  	/////////////////////////////////////////////
   515  	// Compute
   516  	toInt := func(b bool) int {
   517  		if b {
   518  			return 1
   519  		} else {
   520  			return 0
   521  		}
   522  	}
   523  
   524  	isImageUrl := c.ImageUrl != ""
   525  	isCustomManagedImage := c.CustomManagedImageName != "" || c.CustomManagedImageResourceGroupName != ""
   526  	isPlatformImage := c.ImagePublisher != "" || c.ImageOffer != "" || c.ImageSku != ""
   527  
   528  	countSourceInputs := toInt(isImageUrl) + toInt(isCustomManagedImage) + toInt(isPlatformImage)
   529  
   530  	if countSourceInputs > 1 {
   531  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("Specify either a VHD (image_url), Image Reference (image_publisher, image_offer, image_sku) or a Managed Disk (custom_managed_disk_image_name, custom_managed_disk_resource_group_name"))
   532  	}
   533  
   534  	if isImageUrl && c.ManagedImageResourceGroupName != "" {
   535  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("A managed image must be created from a managed image, it cannot be created from a VHD."))
   536  	}
   537  
   538  	if c.ImageUrl == "" && c.CustomManagedImageName == "" {
   539  		if c.ImagePublisher == "" {
   540  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_publisher must be specified"))
   541  		}
   542  		if c.ImageOffer == "" {
   543  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_offer must be specified"))
   544  		}
   545  		if c.ImageSku == "" {
   546  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_sku must be specified"))
   547  		}
   548  	} else if c.ImageUrl == "" && c.ImagePublisher == "" {
   549  		if c.CustomManagedImageResourceGroupName == "" {
   550  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("An custom_managed_image_resource_group_name must be specified"))
   551  		}
   552  		if c.CustomManagedImageName == "" {
   553  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("A custom_managed_image_name must be specified"))
   554  		}
   555  		if c.ManagedImageResourceGroupName == "" {
   556  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("An managed_image_resource_group_name must be specified"))
   557  		}
   558  		if c.ManagedImageName == "" {
   559  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("An managed_image_name must be specified"))
   560  		}
   561  	} else {
   562  		if c.ImagePublisher != "" || c.ImageOffer != "" || c.ImageSku != "" || c.ImageVersion != "" {
   563  			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"))
   564  		}
   565  	}
   566  
   567  	if c.Location == "" {
   568  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("A location must be specified"))
   569  	}
   570  
   571  	/////////////////////////////////////////////
   572  	// Deployment
   573  	xor := func(a, b bool) bool {
   574  		return (a || b) && !(a && b)
   575  	}
   576  
   577  	if !xor((c.StorageAccount != "" || c.ResourceGroupName != ""), (c.ManagedImageName != "" || c.ManagedImageResourceGroupName != "")) {
   578  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("Specify either a VHD (storage_account and resource_group_name) or Managed Image (managed_image_resource_group_name and managed_image_name) output"))
   579  	}
   580  
   581  	if c.ManagedImageName == "" && c.ManagedImageResourceGroupName == "" {
   582  		if c.StorageAccount == "" {
   583  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("A storage_account must be specified"))
   584  		}
   585  		if c.ResourceGroupName == "" {
   586  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("A resource_group_name must be specified"))
   587  		}
   588  	}
   589  
   590  	if c.VirtualNetworkName == "" && c.VirtualNetworkResourceGroupName != "" {
   591  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_resource_group_name is specified, so must virtual_network_name"))
   592  	}
   593  	if c.VirtualNetworkName == "" && c.VirtualNetworkSubnetName != "" {
   594  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_subnet_name is specified, so must virtual_network_name"))
   595  	}
   596  
   597  	/////////////////////////////////////////////
   598  	// OS
   599  	if strings.EqualFold(c.OSType, constants.Target_Linux) {
   600  		c.OSType = constants.Target_Linux
   601  	} else if strings.EqualFold(c.OSType, constants.Target_Windows) {
   602  		c.OSType = constants.Target_Windows
   603  	} else if c.OSType == "" {
   604  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("An os_type must be specified"))
   605  	} else {
   606  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("The os_type %q is invalid", c.OSType))
   607  	}
   608  
   609  	switch c.ManagedImageStorageAccountType {
   610  	case "", string(compute.StandardLRS):
   611  		c.managedImageStorageAccountType = compute.StandardLRS
   612  	case string(compute.PremiumLRS):
   613  		c.managedImageStorageAccountType = compute.PremiumLRS
   614  	default:
   615  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("The managed_image_storage_account_type %q is invalid", c.ManagedImageStorageAccountType))
   616  	}
   617  }