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