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