//go:generate struct-markdown //go:generate mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,DtlArtifact,ArtifactParameter package dtl import ( "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/base64" "encoding/json" "fmt" "math/big" "regexp" "strings" "time" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute" "github.com/Azure/go-autorest/autorest/to" "github.com/masterzen/winrm" "github.com/hashicorp/packer/builder/azure/common/client" "github.com/hashicorp/packer/builder/azure/common/constants" "github.com/hashicorp/packer/builder/azure/pkcs12" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" "golang.org/x/crypto/ssh" ) const ( DefaultImageVersion = "latest" DefaultUserName = "packer" DefaultPrivateVirtualNetworkWithPublicIp = false DefaultVMSize = "Standard_A1" ) const ( // https://docs.microsoft.com/en-us/azure/architecture/best-practices/naming-conventions#naming-rules-and-restrictions // Regular expressions in Go are not expressive enough, such that the regular expression returned by Azure // can be used (no backtracking). // // -> ^[^_\W][\w-._]{0,79}(? 0 { return nil, nil, errs } return &c, nil, nil } func setSshValues(c *Config) error { if c.Comm.SSHTimeout == 0 { c.Comm.SSHTimeout = 20 * time.Minute } if c.Comm.SSHPrivateKeyFile != "" { privateKeyBytes, err := c.Comm.ReadSSHPrivateKeyFile() if err != nil { return err } signer, err := ssh.ParsePrivateKey(privateKeyBytes) if err != nil { return err } publicKey := signer.PublicKey() c.sshAuthorizedKey = fmt.Sprintf("%s %s packer Azure Deployment%s", publicKey.Type(), base64.StdEncoding.EncodeToString(publicKey.Marshal()), time.Now().Format(time.RFC3339)) c.Comm.SSHPrivateKey = privateKeyBytes } else { sshKeyPair, err := NewOpenSshKeyPair() if err != nil { return err } c.sshAuthorizedKey = sshKeyPair.AuthorizedKey() c.Comm.SSHPrivateKey = sshKeyPair.PrivateKey() } return nil } func setWinRMCertificate(c *Config) error { c.Comm.WinRMTransportDecorator = func() winrm.Transporter { return &winrm.ClientNTLM{} } cert, password, err := c.createCertificate() c.winrmCertificate = cert c.winrmPassword = password return err } func setRuntimeValues(c *Config) { var tempName = NewTempName(c) c.tmpAdminPassword = tempName.AdminPassword packer.LogSecretFilter.Set(c.tmpAdminPassword) c.tmpCertificatePassword = tempName.CertificatePassword c.tmpComputeName = tempName.ComputeName c.tmpDeploymentName = tempName.DeploymentName if c.LabResourceGroupName == "" { c.tmpResourceGroupName = tempName.ResourceGroupName } else { c.tmpResourceGroupName = c.LabResourceGroupName } c.tmpNicName = tempName.NicName c.tmpPublicIPAddressName = tempName.PublicIPAddressName c.tmpOSDiskName = tempName.OSDiskName c.tmpSubnetName = tempName.SubnetName c.tmpVirtualNetworkName = tempName.VirtualNetworkName c.tmpKeyVaultName = tempName.KeyVaultName } func setUserNamePassword(c *Config) { if c.Comm.SSHUsername == "" { c.Comm.SSHUsername = DefaultUserName } c.UserName = c.Comm.SSHUsername if c.Comm.SSHPassword != "" { c.Password = c.Comm.SSHPassword } else { c.Password = c.tmpAdminPassword } } func provideDefaultValues(c *Config) { if c.VMSize == "" { c.VMSize = DefaultVMSize } if c.ManagedImageStorageAccountType == "" { c.managedImageStorageAccountType = compute.StorageAccountTypesStandardLRS } if c.DiskCachingType == "" { c.diskCachingType = compute.CachingTypesReadWrite } if c.ImagePublisher != "" && c.ImageVersion == "" { c.ImageVersion = DefaultImageVersion } } func assertTagProperties(c *Config, errs *packer.MultiError) { if len(c.AzureTags) > 15 { errs = packer.MultiErrorAppend(errs, fmt.Errorf("a max of 15 tags are supported, but %d were provided", len(c.AzureTags))) } for k, v := range c.AzureTags { if len(k) > 512 { errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 512 character limit", k, len(k))) } if len(*v) > 256 { errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", *v, len(*v))) } } } func assertRequiredParametersSet(c *Config, errs *packer.MultiError) { c.ClientConfig.Validate(errs) ///////////////////////////////////////////// // Capture if c.CaptureContainerName == "" && c.ManagedImageName == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name or managed_image_name must be specified")) } if c.CaptureNamePrefix == "" && c.ManagedImageResourceGroupName == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix or managed_image_resource_group_name must be specified")) } if (c.CaptureNamePrefix != "" || c.CaptureContainerName != "") && (c.ManagedImageResourceGroupName != "" || c.ManagedImageName != "") { 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.")) } if c.CaptureContainerName != "" { if !reCaptureContainerName.MatchString(c.CaptureContainerName) { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must satisfy the regular expression %q.", reCaptureContainerName.String())) } if strings.HasSuffix(c.CaptureContainerName, "-") { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not end with a hyphen, e.g. '-'.")) } if strings.Contains(c.CaptureContainerName, "--") { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not contain consecutive hyphens, e.g. '--'.")) } if c.CaptureNamePrefix == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must be specified")) } if !reCaptureNamePrefix.MatchString(c.CaptureNamePrefix) { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must satisfy the regular expression %q.", reCaptureNamePrefix.String())) } if strings.HasSuffix(c.CaptureNamePrefix, "-") || strings.HasSuffix(c.CaptureNamePrefix, ".") { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must not end with a hyphen or period.")) } } if c.LabResourceGroupName == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("The settings lab_resource_group_name needs to be defined.")) } ///////////////////////////////////////////// // Compute toInt := func(b bool) int { if b { return 1 } else { return 0 } } isImageUrl := c.ImageUrl != "" isCustomManagedImage := c.CustomManagedImageName != "" || c.CustomManagedImageResourceGroupName != "" isSharedGallery := c.SharedGallery.GalleryName != "" isPlatformImage := c.ImagePublisher != "" || c.ImageOffer != "" || c.ImageSku != "" countSourceInputs := toInt(isImageUrl) + toInt(isCustomManagedImage) + toInt(isPlatformImage) + toInt(isSharedGallery) if countSourceInputs > 1 { 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)")) } if isImageUrl && c.ManagedImageResourceGroupName != "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A managed image must be created from a managed image, it cannot be created from a VHD.")) } if c.SharedGallery.GalleryName != "" { if c.SharedGallery.Subscription == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A shared_image_gallery.subscription must be specified")) } if c.SharedGallery.ResourceGroup == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A shared_image_gallery.resource_group must be specified")) } if c.SharedGallery.ImageName == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A shared_image_gallery.image_name must be specified")) } if c.CaptureContainerName != "" { 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.")) } if c.CaptureNamePrefix != "" { 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.")) } } else if c.ImageUrl == "" && c.CustomManagedImageName == "" { if c.ImagePublisher == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_publisher must be specified")) } if c.ImageOffer == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_offer must be specified")) } if c.ImageSku == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_sku must be specified")) } } else if c.ImageUrl == "" && c.ImagePublisher == "" { if c.CustomManagedImageResourceGroupName == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("An custom_managed_image_resource_group_name must be specified")) } if c.CustomManagedImageName == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("A custom_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.ManagedImageName == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("An managed_image_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")) } } if c.ManagedImageResourceGroupName != "" { if ok, err := assertResourceGroupName(c.ManagedImageResourceGroupName, "managed_image_resource_group_name"); !ok { errs = packer.MultiErrorAppend(errs, err) } } if c.ManagedImageName != "" { if ok, err := assertManagedImageName(c.ManagedImageName, "managed_image_name"); !ok { errs = packer.MultiErrorAppend(errs, err) } } if c.LabVirtualNetworkName == "" && c.LabResourceGroupName != "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("If lab_resource_group_name is specified, so must lab_virtual_network_name")) } if c.LabVirtualNetworkName == "" && c.LabSubnetName != "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_subnet_name is specified, so must lab_virtual_network_name")) } ///////////////////////////////////////////// // Polling Duration Timeout if c.PollingDurationTimeout == 0 { // In the sdk, the default is 15 m. c.PollingDurationTimeout = 15 * time.Minute } ///////////////////////////////////////////// // OS if strings.EqualFold(c.OSType, constants.Target_Linux) { c.OSType = constants.Target_Linux } else if strings.EqualFold(c.OSType, constants.Target_Windows) { c.OSType = constants.Target_Windows } else if c.OSType == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("An os_type must be specified")) } else { errs = packer.MultiErrorAppend(errs, fmt.Errorf("The os_type %q is invalid", c.OSType)) } switch c.ManagedImageStorageAccountType { case "", string(compute.StorageAccountTypesStandardLRS): c.managedImageStorageAccountType = compute.StorageAccountTypesStandardLRS case string(compute.StorageAccountTypesPremiumLRS): c.managedImageStorageAccountType = compute.StorageAccountTypesPremiumLRS default: errs = packer.MultiErrorAppend(errs, fmt.Errorf("The managed_image_storage_account_type %q is invalid", c.ManagedImageStorageAccountType)) } // Errs check to make the linter happy. if errs != nil { return } } func assertManagedImageName(name, setting string) (bool, error) { if !isValidAzureName(reManagedDiskName, name) { return false, fmt.Errorf("The setting %s must match the regular expression %q, and not end with a '-' or '.'.", setting, validManagedDiskName) } return true, nil } func assertResourceGroupName(rgn, setting string) (bool, error) { if !isValidAzureName(reResourceGroupName, rgn) { return false, fmt.Errorf("The setting %s must match the regular expression %q, and not end with a '-' or '.'.", setting, validResourceGroupNameRe) } return true, nil } func isValidAzureName(re *regexp.Regexp, rgn string) bool { return re.Match([]byte(rgn)) && !strings.HasSuffix(rgn, ".") && !strings.HasSuffix(rgn, "-") } func (c *Config) validateLocationZoneResiliency(say func(s string)) { // Docs on regions that support Availibility Zones: // https://docs.microsoft.com/en-us/azure/availability-zones/az-overview#regions-that-support-availability-zones // Query technical names for locations: // az account list-locations --query '[].name' -o tsv var zones = make(map[string]struct{}) zones["westeurope"] = struct{}{} zones["centralus"] = struct{}{} zones["eastus2"] = struct{}{} zones["francecentral"] = struct{}{} zones["northeurope"] = struct{}{} zones["southeastasia"] = struct{}{} zones["westus2"] = struct{}{} if _, ok := zones[c.Location]; !ok { say(fmt.Sprintf("WARNING: Zone resiliency may not be supported in %s, checkout the docs at https://docs.microsoft.com/en-us/azure/availability-zones/", c.Location)) } }