2016-03-04 05:14:55 -05:00
package arm
import (
2016-04-21 19:50:03 -04:00
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
2016-03-04 05:14:55 -05:00
"encoding/base64"
2016-04-21 19:50:03 -04:00
"encoding/json"
2016-03-04 05:14:55 -05:00
"fmt"
"io/ioutil"
2016-04-21 19:50:03 -04:00
"math/big"
2016-05-18 20:25:57 -04:00
"regexp"
"strings"
2016-03-04 05:14:55 -05:00
"time"
2018-04-06 04:12:58 -04:00
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
2016-03-04 05:14:55 -05:00
"github.com/Azure/go-autorest/autorest/to"
2017-01-18 16:11:48 -05:00
"github.com/masterzen/winrm"
2016-05-18 20:25:57 -04:00
2017-04-04 16:39:01 -04:00
"github.com/hashicorp/packer/builder/azure/common/constants"
"github.com/hashicorp/packer/builder/azure/pkcs12"
"github.com/hashicorp/packer/common"
2018-09-10 19:48:42 -04:00
commonhelper "github.com/hashicorp/packer/helper/common"
2017-04-04 16:39:01 -04:00
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
2016-04-21 19:50:03 -04:00
"golang.org/x/crypto/ssh"
2016-03-04 05:14:55 -05:00
)
const (
2017-08-06 18:32:44 -04:00
DefaultImageVersion = "latest"
DefaultUserName = "packer"
DefaultPrivateVirtualNetworkWithPublicIp = false
DefaultVMSize = "Standard_A1"
2016-03-04 05:14:55 -05:00
)
2017-11-17 12:49:23 -05:00
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}(?<![-.])$
//
// This is not an exhaustive match, but it should be extremely close.
2018-07-11 11:31:51 -04:00
validResourceGroupNameRe = "^[^_\\W][\\w-._\\(\\)]{0,89}$"
2017-11-17 12:49:23 -05:00
validManagedDiskName = "^[^_\\W][\\w-._)]{0,79}$"
)
2016-05-18 20:25:57 -04:00
var (
reCaptureContainerName = regexp . MustCompile ( "^[a-z0-9][a-z0-9\\-]{2,62}$" )
reCaptureNamePrefix = regexp . MustCompile ( "^[A-Za-z0-9][A-Za-z0-9_\\-\\.]{0,23}$" )
2017-11-17 12:49:23 -05:00
reManagedDiskName = regexp . MustCompile ( validManagedDiskName )
reResourceGroupName = regexp . MustCompile ( validResourceGroupNameRe )
2018-12-13 16:54:19 -05:00
reSnapshotName = regexp . MustCompile ( "^[A-Za-z0-9_]{1,79}$" )
reSnapshotPrefix = regexp . MustCompile ( "^[A-Za-z0-9_]{1,59}$" )
2016-05-18 20:25:57 -04:00
)
2018-03-09 01:39:23 -05:00
type PlanInformation struct {
PlanName string ` mapstructure:"plan_name" `
PlanProduct string ` mapstructure:"plan_product" `
PlanPublisher string ` mapstructure:"plan_publisher" `
PlanPromotionCode string ` mapstructure:"plan_promotion_code" `
}
2018-10-09 16:56:49 -04:00
type SharedImageGallery struct {
Subscription string ` mapstructure:"subscription" `
ResourceGroup string ` mapstructure:"resource_group" `
GalleryName string ` mapstructure:"gallery_name" `
ImageName string ` mapstructure:"image_name" `
ImageVersion string ` mapstructure:"image_version" `
}
2019-06-03 20:47:29 -04:00
type SharedImageGalleryDestination struct {
SigDestinationResourceGroup string ` mapstructure:"resource_group" `
SigDestinationGalleryName string ` mapstructure:"gallery_name" `
SigDestinationImageName string ` mapstructure:"image_name" `
SigDestinationImageVersion string ` mapstructure:"image_version" `
SigDestinationReplicationRegions [ ] string ` mapstructure:"replication_regions" `
}
2016-03-04 05:14:55 -05:00
type Config struct {
common . PackerConfig ` mapstructure:",squash" `
// Authentication via OAUTH
2019-01-10 18:21:16 -05:00
ClientConfig ` mapstructure:",squash" `
2016-03-04 05:14:55 -05:00
// Capture
CaptureNamePrefix string ` mapstructure:"capture_name_prefix" `
CaptureContainerName string ` mapstructure:"capture_container_name" `
2018-10-05 19:04:07 -04:00
// Shared Gallery
2018-10-09 16:56:49 -04:00
SharedGallery SharedImageGallery ` mapstructure:"shared_image_gallery" `
2018-09-30 16:56:44 -04:00
2019-06-03 20:47:29 -04:00
// Shared Gallery Destination
SharedGalleryDestination SharedImageGalleryDestination ` mapstructure:"shared_image_gallery_destination" `
2018-10-05 19:04:07 -04:00
// Compute
2016-03-04 05:14:55 -05:00
ImagePublisher string ` mapstructure:"image_publisher" `
ImageOffer string ` mapstructure:"image_offer" `
ImageSku string ` mapstructure:"image_sku" `
2016-04-21 19:50:03 -04:00
ImageVersion string ` mapstructure:"image_version" `
2016-05-21 02:01:16 -04:00
ImageUrl string ` mapstructure:"image_url" `
2017-05-29 00:06:09 -04:00
2017-05-30 14:25:46 -04:00
CustomManagedImageResourceGroupName string ` mapstructure:"custom_managed_image_resource_group_name" `
CustomManagedImageName string ` mapstructure:"custom_managed_image_name" `
2017-06-22 19:36:40 -04:00
customManagedImageID string
2017-05-29 00:06:09 -04:00
Location string ` mapstructure:"location" `
VMSize string ` mapstructure:"vm_size" `
2018-11-06 14:17:03 -05:00
ManagedImageResourceGroupName string ` mapstructure:"managed_image_resource_group_name" `
ManagedImageName string ` mapstructure:"managed_image_name" `
ManagedImageStorageAccountType string ` mapstructure:"managed_image_storage_account_type" `
2018-11-30 15:32:41 -05:00
managedImageStorageAccountType compute . StorageAccountTypes
2018-11-06 14:17:03 -05:00
ManagedImageOSDiskSnapshotName string ` mapstructure:"managed_image_os_disk_snapshot_name" `
2018-11-05 18:48:22 -05:00
ManagedImageDataDiskSnapshotPrefix string ` mapstructure:"managed_image_data_disk_snapshot_prefix" `
2018-11-06 14:17:03 -05:00
manageImageLocation string
2019-01-17 03:01:42 -05:00
ManagedImageZoneResilient bool ` mapstructure:"managed_image_zone_resilient" `
2016-03-04 05:14:55 -05:00
// Deployment
2017-08-06 18:32:44 -04:00
AzureTags map [ string ] * string ` mapstructure:"azure_tags" `
ResourceGroupName string ` mapstructure:"resource_group_name" `
StorageAccount string ` mapstructure:"storage_account" `
TempComputeName string ` mapstructure:"temp_compute_name" `
TempResourceGroupName string ` mapstructure:"temp_resource_group_name" `
2017-11-09 06:20:09 -05:00
BuildResourceGroupName string ` mapstructure:"build_resource_group_name" `
2017-08-06 18:32:44 -04:00
storageAccountBlobEndpoint string
PrivateVirtualNetworkWithPublicIp bool ` mapstructure:"private_virtual_network_with_public_ip" `
VirtualNetworkName string ` mapstructure:"virtual_network_name" `
VirtualNetworkSubnetName string ` mapstructure:"virtual_network_subnet_name" `
VirtualNetworkResourceGroupName string ` mapstructure:"virtual_network_resource_group_name" `
CustomDataFile string ` mapstructure:"custom_data_file" `
customData string
2018-03-09 01:39:23 -05:00
PlanInfo PlanInformation ` mapstructure:"plan_info" `
2016-04-21 19:50:03 -04:00
// OS
2016-10-12 19:24:04 -04:00
OSType string ` mapstructure:"os_type" `
OSDiskSizeGB int32 ` mapstructure:"os_disk_size_gb" `
2016-03-04 05:14:55 -05:00
2018-02-23 18:34:13 -05:00
// Additional Disks
AdditionalDiskSize [ ] int32 ` mapstructure:"disk_additional_size" `
2018-11-30 15:32:41 -05:00
DiskCachingType string ` mapstructure:"disk_caching_type" `
diskCachingType compute . CachingTypes
2018-02-23 18:34:13 -05:00
2016-03-04 05:14:55 -05:00
// Runtime Values
2016-04-21 19:50:03 -04:00
UserName string
Password string
tmpAdminPassword string
tmpCertificatePassword string
tmpResourceGroupName string
tmpComputeName string
2018-03-10 14:17:43 -05:00
tmpNicName string
tmpPublicIPAddressName string
2016-04-21 19:50:03 -04:00
tmpDeploymentName string
tmpKeyVaultName string
tmpOSDiskName string
2018-03-10 14:17:43 -05:00
tmpSubnetName string
tmpVirtualNetworkName string
2016-04-21 19:50:03 -04:00
tmpWinRMCertificateUrl string
2016-03-04 05:14:55 -05:00
// Authentication with the VM via SSH
sshAuthorizedKey string
2016-04-21 19:50:03 -04:00
// Authentication with the VM via WinRM
winrmCertificate string
2016-03-04 05:14:55 -05:00
Comm communicator . Config ` mapstructure:",squash" `
2018-08-01 02:35:29 -04:00
ctx interpolate . Context
2018-05-14 23:06:23 -04:00
//Cleanup
2018-05-15 14:41:26 -04:00
AsyncResourceGroupDelete bool ` mapstructure:"async_resourcegroup_delete" `
2016-03-04 05:14:55 -05:00
}
2016-04-21 19:50:03 -04:00
type keyVaultCertificate struct {
Data string ` json:"data" `
DataType string ` json:"dataType" `
Password string ` json:"password,omitempty" `
}
2017-05-29 00:06:09 -04:00
func ( c * Config ) toVMID ( ) string {
2017-11-09 06:20:09 -05:00
var resourceGroupName string
if c . tmpResourceGroupName != "" {
resourceGroupName = c . tmpResourceGroupName
} else {
resourceGroupName = c . BuildResourceGroupName
}
return fmt . Sprintf ( "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s" , c . SubscriptionID , resourceGroupName , c . tmpComputeName )
2017-05-29 00:06:09 -04:00
}
func ( c * Config ) isManagedImage ( ) bool {
2017-05-30 14:25:46 -04:00
return c . ManagedImageName != ""
2017-05-29 00:06:09 -04:00
}
2016-03-04 05:14:55 -05:00
func ( c * Config ) toVirtualMachineCaptureParameters ( ) * compute . VirtualMachineCaptureParameters {
return & compute . VirtualMachineCaptureParameters {
DestinationContainerName : & c . CaptureContainerName ,
VhdPrefix : & c . CaptureNamePrefix ,
OverwriteVhds : to . BoolPtr ( false ) ,
}
}
2017-05-29 00:06:09 -04:00
func ( c * Config ) toImageParameters ( ) * compute . Image {
return & compute . Image {
ImageProperties : & compute . ImageProperties {
SourceVirtualMachine : & compute . SubResource {
ID : to . StringPtr ( c . toVMID ( ) ) ,
} ,
2019-01-17 03:01:42 -05:00
StorageProfile : & compute . ImageStorageProfile {
ZoneResilient : to . BoolPtr ( c . ManagedImageZoneResilient ) ,
} ,
2017-05-29 00:06:09 -04:00
} ,
Location : to . StringPtr ( c . Location ) ,
2018-04-06 04:12:58 -04:00
Tags : c . AzureTags ,
2017-05-29 00:06:09 -04:00
}
}
2016-04-21 19:50:03 -04:00
func ( c * Config ) createCertificate ( ) ( string , error ) {
privateKey , err := rsa . GenerateKey ( rand . Reader , 2048 )
if err != nil {
2016-07-16 01:23:53 -04:00
err = fmt . Errorf ( "Failed to Generate Private Key: %s" , err )
2016-04-21 19:50:03 -04:00
return "" , err
}
host := fmt . Sprintf ( "%s.cloudapp.net" , c . tmpComputeName )
notBefore := time . Now ( )
2016-05-17 17:15:24 -04:00
notAfter := notBefore . Add ( 24 * time . Hour )
2016-04-21 19:50:03 -04:00
serialNumber , err := rand . Int ( rand . Reader , new ( big . Int ) . Lsh ( big . NewInt ( 1 ) , 128 ) )
if err != nil {
2016-07-16 01:23:53 -04:00
err = fmt . Errorf ( "Failed to Generate Serial Number: %v" , err )
2016-04-21 19:50:03 -04:00
return "" , err
}
template := x509 . Certificate {
SerialNumber : serialNumber ,
Issuer : pkix . Name {
CommonName : host ,
} ,
Subject : pkix . Name {
CommonName : host ,
} ,
NotBefore : notBefore ,
NotAfter : notAfter ,
KeyUsage : x509 . KeyUsageKeyEncipherment | x509 . KeyUsageDigitalSignature ,
ExtKeyUsage : [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageServerAuth } ,
BasicConstraintsValid : true ,
}
derBytes , err := x509 . CreateCertificate ( rand . Reader , & template , & template , & privateKey . PublicKey , privateKey )
if err != nil {
err = fmt . Errorf ( "Failed to Create Certificate: %s" , err )
return "" , err
}
pfxBytes , err := pkcs12 . Encode ( derBytes , privateKey , c . tmpCertificatePassword )
if err != nil {
err = fmt . Errorf ( "Failed to encode certificate as PFX: %s" , err )
return "" , err
}
keyVaultDescription := keyVaultCertificate {
Data : base64 . StdEncoding . EncodeToString ( pfxBytes ) ,
DataType : "pfx" ,
Password : c . tmpCertificatePassword ,
}
bytes , err := json . Marshal ( keyVaultDescription )
if err != nil {
err = fmt . Errorf ( "Failed to marshal key vault description: %s" , err )
return "" , err
}
return base64 . StdEncoding . EncodeToString ( bytes ) , nil
}
2016-03-04 05:14:55 -05:00
func newConfig ( raws ... interface { } ) ( * Config , [ ] string , error ) {
var c Config
2018-08-01 02:35:29 -04:00
c . ctx . Funcs = TemplateFuncs
2016-03-04 05:14:55 -05:00
err := config . Decode ( & c , & config . DecodeOpts {
Interpolate : true ,
2018-08-01 02:35:29 -04:00
InterpolateContext : & c . ctx ,
2016-03-04 05:14:55 -05:00
} , raws ... )
if err != nil {
return nil , nil , err
}
provideDefaultValues ( & c )
setRuntimeValues ( & c )
setUserNamePassword ( & c )
2019-01-10 18:21:16 -05:00
err = c . ClientConfig . setCloudEnvironment ( )
2016-04-21 19:50:03 -04:00
if err != nil {
return nil , nil , err
}
2016-03-04 05:14:55 -05:00
2016-10-13 14:56:23 -04:00
err = setCustomData ( & c )
if err != nil {
return nil , nil , err
}
2016-05-17 16:53:01 -04:00
// NOTE: if the user did not specify a communicator, then default to both
// SSH and WinRM. This is for backwards compatibility because the code did
2016-05-21 02:01:16 -04:00
// not specifically force the user to set a communicator.
2016-05-17 16:53:01 -04:00
if c . Comm . Type == "" || strings . EqualFold ( c . Comm . Type , "ssh" ) {
err = setSshValues ( & c )
if err != nil {
return nil , nil , err
}
2016-03-04 05:14:55 -05:00
}
2016-05-17 16:53:01 -04:00
if c . Comm . Type == "" || strings . EqualFold ( c . Comm . Type , "winrm" ) {
err = setWinRMCertificate ( & c )
if err != nil {
return nil , nil , err
}
2016-04-21 19:50:03 -04:00
}
2016-03-04 05:14:55 -05:00
var errs * packer . MultiError
2018-08-01 02:35:29 -04:00
errs = packer . MultiErrorAppend ( errs , c . Comm . Prepare ( & c . ctx ) ... )
2016-03-04 05:14:55 -05:00
assertRequiredParametersSet ( & c , errs )
2016-07-29 17:17:33 -04:00
assertTagProperties ( & c , errs )
2016-03-04 05:14:55 -05:00
if errs != nil && len ( errs . Errors ) > 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
}
2018-08-23 10:35:07 -04:00
if c . Comm . SSHPrivateKeyFile != "" {
2018-11-06 14:03:33 -05:00
privateKeyBytes , err := c . Comm . ReadSSHPrivateKeyFile ( )
2016-03-04 05:14:55 -05:00
if err != nil {
2017-04-27 14:04:57 -04:00
return err
2016-03-04 05:14:55 -05:00
}
signer , err := ssh . ParsePrivateKey ( privateKeyBytes )
if err != nil {
2017-04-27 14:04:57 -04:00
return err
2016-03-04 05:14:55 -05:00
}
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 ) )
2018-08-24 03:17:47 -04:00
c . Comm . SSHPrivateKey = privateKeyBytes
2016-03-04 05:14:55 -05:00
} else {
sshKeyPair , err := NewOpenSshKeyPair ( )
if err != nil {
return err
}
c . sshAuthorizedKey = sshKeyPair . AuthorizedKey ( )
2018-08-24 03:17:47 -04:00
c . Comm . SSHPrivateKey = sshKeyPair . PrivateKey ( )
2016-03-04 05:14:55 -05:00
}
return nil
}
2016-04-21 19:50:03 -04:00
func setWinRMCertificate ( c * Config ) error {
2017-01-18 16:11:48 -05:00
c . Comm . WinRMTransportDecorator =
2017-05-29 00:06:09 -04:00
func ( ) winrm . Transporter {
return & winrm . ClientNTLM { }
}
2016-05-17 16:53:01 -04:00
2016-04-21 19:50:03 -04:00
cert , err := c . createCertificate ( )
c . winrmCertificate = cert
return err
}
2016-03-04 05:14:55 -05:00
func setRuntimeValues ( c * Config ) {
var tempName = NewTempName ( )
c . tmpAdminPassword = tempName . AdminPassword
2018-09-10 19:48:42 -04:00
// store so that we can access this later during provisioning
commonhelper . SetSharedState ( "winrm_password" , c . tmpAdminPassword , c . PackerConfig . PackerBuildName )
2018-08-10 17:25:14 -04:00
packer . LogSecretFilter . Set ( c . tmpAdminPassword )
2018-09-10 19:48:42 -04:00
2016-04-21 19:50:03 -04:00
c . tmpCertificatePassword = tempName . CertificatePassword
2017-01-27 06:26:28 -05:00
if c . TempComputeName == "" {
c . tmpComputeName = tempName . ComputeName
} else {
c . tmpComputeName = c . TempComputeName
}
2016-03-04 05:14:55 -05:00
c . tmpDeploymentName = tempName . DeploymentName
2017-11-09 06:20:09 -05:00
// Only set tmpResourceGroupName if no name has been specified
if c . TempResourceGroupName == "" && c . BuildResourceGroupName == "" {
2017-01-27 06:26:28 -05:00
c . tmpResourceGroupName = tempName . ResourceGroupName
2017-11-09 06:20:09 -05:00
} else if c . TempResourceGroupName != "" && c . BuildResourceGroupName == "" {
2017-01-27 06:26:28 -05:00
c . tmpResourceGroupName = c . TempResourceGroupName
}
2018-03-10 14:17:43 -05:00
c . tmpNicName = tempName . NicName
c . tmpPublicIPAddressName = tempName . PublicIPAddressName
2016-03-04 05:14:55 -05:00
c . tmpOSDiskName = tempName . OSDiskName
2018-03-10 14:17:43 -05:00
c . tmpSubnetName = tempName . SubnetName
c . tmpVirtualNetworkName = tempName . VirtualNetworkName
2016-04-21 19:50:03 -04:00
c . tmpKeyVaultName = tempName . KeyVaultName
2016-03-04 05:14:55 -05:00
}
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
}
}
2016-10-13 14:56:23 -04:00
func setCustomData ( c * Config ) error {
if c . CustomDataFile == "" {
return nil
}
b , err := ioutil . ReadFile ( c . CustomDataFile )
if err != nil {
return err
}
c . customData = base64 . StdEncoding . EncodeToString ( b )
return nil
}
2016-03-04 05:14:55 -05:00
func provideDefaultValues ( c * Config ) {
if c . VMSize == "" {
c . VMSize = DefaultVMSize
}
2016-04-21 19:50:03 -04:00
2017-08-13 04:45:04 -04:00
if c . ManagedImageStorageAccountType == "" {
2018-04-06 04:12:58 -04:00
c . managedImageStorageAccountType = compute . StorageAccountTypesStandardLRS
2017-08-13 04:45:04 -04:00
}
2018-11-30 15:32:41 -05:00
if c . DiskCachingType == "" {
c . diskCachingType = compute . CachingTypesReadWrite
}
2017-05-29 00:06:09 -04:00
if c . ImagePublisher != "" && c . ImageVersion == "" {
2016-04-21 19:50:03 -04:00
c . ImageVersion = DefaultImageVersion
}
2019-01-10 18:21:16 -05:00
c . provideDefaultValues ( )
2016-03-04 05:14:55 -05:00
}
2016-07-29 17:17:33 -04:00
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 {
2018-08-24 19:18:21 -04:00
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "the tag name %q exceeds (%d) the 256 character limit" , * v , len ( * v ) ) )
2016-07-29 17:17:33 -04:00
}
}
}
2016-03-04 05:14:55 -05:00
func assertRequiredParametersSet ( c * Config , errs * packer . MultiError ) {
2019-01-10 18:21:16 -05:00
c . ClientConfig . assertRequiredParametersSet ( errs )
2016-03-04 05:14:55 -05:00
/////////////////////////////////////////////
// Capture
2017-06-07 18:01:10 -04:00
if c . CaptureContainerName == "" && c . ManagedImageName == "" {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "A capture_container_name or managed_image_name must be specified" ) )
2016-05-18 20:25:57 -04:00
}
2017-06-07 18:01:10 -04:00
if c . CaptureNamePrefix == "" && c . ManagedImageResourceGroupName == "" {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "A capture_name_prefix or managed_image_resource_group_name must be specified" ) )
2016-05-18 20:25:57 -04:00
}
2017-06-07 18:01:10 -04:00
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." ) )
2016-05-18 20:25:57 -04:00
}
2017-06-07 18:01:10 -04:00
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 ( ) ) )
}
2016-03-04 05:14:55 -05:00
2017-06-07 18:01:10 -04:00
if strings . HasSuffix ( c . CaptureContainerName , "-" ) {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "A capture_container_name must not end with a hyphen, e.g. '-'." ) )
}
2016-05-18 20:25:57 -04:00
2017-06-07 18:01:10 -04:00
if strings . Contains ( c . CaptureContainerName , "--" ) {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "A capture_container_name must not contain consecutive hyphens, e.g. '--'." ) )
}
2016-05-18 20:25:57 -04:00
2017-06-07 18:01:10 -04:00
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." ) )
}
2016-03-04 05:14:55 -05:00
}
2017-11-16 19:34:13 -05:00
if c . TempResourceGroupName != "" && c . BuildResourceGroupName != "" {
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." ) )
}
2016-03-04 05:14:55 -05:00
/////////////////////////////////////////////
// Compute
2017-06-07 18:01:10 -04:00
toInt := func ( b bool ) int {
if b {
return 1
} else {
return 0
}
}
isImageUrl := c . ImageUrl != ""
isCustomManagedImage := c . CustomManagedImageName != "" || c . CustomManagedImageResourceGroupName != ""
2018-10-09 16:56:49 -04:00
isSharedGallery := c . SharedGallery . GalleryName != ""
2017-06-07 18:01:10 -04:00
isPlatformImage := c . ImagePublisher != "" || c . ImageOffer != "" || c . ImageSku != ""
2018-10-03 19:10:46 -04:00
countSourceInputs := toInt ( isImageUrl ) + toInt ( isCustomManagedImage ) + toInt ( isPlatformImage ) + toInt ( isSharedGallery )
2017-06-07 18:01:10 -04:00
if countSourceInputs > 1 {
2018-10-09 16:56:49 -04:00
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)" ) )
2017-05-29 00:06:09 -04:00
}
2017-08-13 03:27:51 -04:00
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." ) )
}
2018-10-09 16:56:49 -04:00
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" ) )
2018-10-03 19:10:46 -04:00
}
2018-10-09 16:56:49 -04:00
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." ) )
2018-10-03 19:10:46 -04:00
}
2018-10-09 16:56:49 -04:00
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." ) )
2018-10-03 19:10:46 -04:00
}
} else if c . ImageUrl == "" && c . CustomManagedImageName == "" {
2016-05-21 02:01:16 -04:00
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" ) )
}
2017-05-29 00:06:09 -04:00
} else if c . ImageUrl == "" && c . ImagePublisher == "" {
2017-05-30 14:25:46 -04:00
if c . CustomManagedImageResourceGroupName == "" {
2019-06-03 20:47:29 -04:00
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "A custom_managed_image_resource_group_name must be specified" ) )
2017-05-30 14:25:46 -04:00
}
if c . CustomManagedImageName == "" {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "A custom_managed_image_name must be specified" ) )
2017-05-29 00:06:09 -04:00
}
if c . ManagedImageResourceGroupName == "" {
2019-06-03 20:47:29 -04:00
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "A managed_image_resource_group_name must be specified" ) )
2017-05-29 00:06:09 -04:00
}
2017-05-30 14:25:46 -04:00
if c . ManagedImageName == "" {
2019-06-03 20:47:29 -04:00
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "A managed_image_name must be specified" ) )
2017-05-29 00:06:09 -04:00
}
2016-05-21 02:01:16 -04:00
} 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" ) )
}
2016-03-04 05:14:55 -05:00
}
/////////////////////////////////////////////
// Deployment
2017-06-07 18:01:10 -04:00
xor := func ( a , b bool ) bool {
return ( a || b ) && ! ( a && b )
2016-03-04 05:14:55 -05:00
}
2017-06-07 18:01:10 -04:00
if ! xor ( ( c . StorageAccount != "" || c . ResourceGroupName != "" ) , ( c . ManagedImageName != "" || c . ManagedImageResourceGroupName != "" ) ) {
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" ) )
2016-06-09 04:00:23 -04:00
}
2017-06-07 18:01:10 -04:00
2017-11-30 19:43:35 -05:00
if ! xor ( c . Location != "" , c . BuildResourceGroupName != "" ) {
2018-03-05 00:21:02 -05:00
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." ) )
2017-11-30 19:43:35 -05:00
}
2017-06-07 18:01:10 -04:00
if c . ManagedImageName == "" && c . ManagedImageResourceGroupName == "" {
if c . StorageAccount == "" {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "A storage_account must be specified" ) )
}
if c . ResourceGroupName == "" {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "A resource_group_name must be specified" ) )
}
}
2017-11-17 12:49:23 -05:00
if c . TempResourceGroupName != "" {
if ok , err := assertResourceGroupName ( c . TempResourceGroupName , "temp_resource_group_name" ) ; ! ok {
errs = packer . MultiErrorAppend ( errs , err )
}
}
if c . BuildResourceGroupName != "" {
if ok , err := assertResourceGroupName ( c . BuildResourceGroupName , "build_resource_group_name" ) ; ! ok {
errs = packer . MultiErrorAppend ( errs , err )
}
}
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 )
}
}
2019-06-03 20:47:29 -04:00
if c . SharedGalleryDestination . SigDestinationGalleryName != "" {
if c . SharedGalleryDestination . SigDestinationResourceGroup == "" {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "A resource_group must be specified for shared_image_gallery_destination" ) )
}
if c . SharedGalleryDestination . SigDestinationImageName == "" {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "An image_name must be specified for shared_image_gallery_destination" ) )
}
if c . SharedGalleryDestination . SigDestinationImageVersion == "" {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "An image_version must be specified for shared_image_gallery_destination" ) )
}
if len ( c . SharedGalleryDestination . SigDestinationReplicationRegions ) == 0 {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "A list of replication_regions must be specified for shared_image_gallery_destination" ) )
}
}
2018-11-05 18:48:22 -05:00
if c . ManagedImageOSDiskSnapshotName != "" {
2018-11-06 14:17:03 -05:00
if ok , err := assertManagedImageOSDiskSnapshotName ( c . ManagedImageOSDiskSnapshotName , "managed_image_os_disk_snapshot_name" ) ; ! ok {
2018-11-05 18:48:22 -05:00
errs = packer . MultiErrorAppend ( errs , err )
}
}
if c . ManagedImageDataDiskSnapshotPrefix != "" {
2018-11-06 14:17:03 -05:00
if ok , err := assertManagedImageDataDiskSnapshotName ( c . ManagedImageDataDiskSnapshotPrefix , "managed_image_data_disk_snapshot_prefix" ) ; ! ok {
2018-11-05 18:48:22 -05:00
errs = packer . MultiErrorAppend ( errs , err )
}
}
2016-06-30 19:51:52 -04:00
if c . VirtualNetworkName == "" && c . VirtualNetworkResourceGroupName != "" {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "If virtual_network_resource_group_name is specified, so must virtual_network_name" ) )
}
if c . VirtualNetworkName == "" && c . VirtualNetworkSubnetName != "" {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "If virtual_network_subnet_name is specified, so must virtual_network_name" ) )
}
2016-04-21 19:50:03 -04:00
2018-03-05 04:27:52 -05:00
/////////////////////////////////////////////
// Plan Info
2018-03-09 01:39:23 -05:00
if c . PlanInfo . PlanName != "" || c . PlanInfo . PlanProduct != "" || c . PlanInfo . PlanPublisher != "" || c . PlanInfo . PlanPromotionCode != "" {
if c . PlanInfo . PlanName == "" || c . PlanInfo . PlanProduct == "" || c . PlanInfo . PlanPublisher == "" {
2018-03-05 04:27:52 -05:00
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" ) )
2018-03-09 01:39:23 -05:00
} else {
if c . AzureTags == nil {
c . AzureTags = make ( map [ string ] * string )
}
c . AzureTags [ "PlanInfo" ] = & c . PlanInfo . PlanName
c . AzureTags [ "PlanProduct" ] = & c . PlanInfo . PlanProduct
c . AzureTags [ "PlanPublisher" ] = & c . PlanInfo . PlanPublisher
c . AzureTags [ "PlanPromotionCode" ] = & c . PlanInfo . PlanPromotionCode
2018-03-05 04:27:52 -05:00
}
}
2016-04-21 19:50:03 -04:00
/////////////////////////////////////////////
// OS
2016-10-12 20:54:59 -04:00
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 == "" {
2016-04-21 19:50:03 -04:00
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "An os_type must be specified" ) )
2016-10-12 20:54:59 -04:00
} else {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "The os_type %q is invalid" , c . OSType ) )
2016-04-21 19:50:03 -04:00
}
2017-08-13 04:45:04 -04:00
switch c . ManagedImageStorageAccountType {
2018-04-06 04:12:58 -04:00
case "" , string ( compute . StorageAccountTypesStandardLRS ) :
c . managedImageStorageAccountType = compute . StorageAccountTypesStandardLRS
case string ( compute . StorageAccountTypesPremiumLRS ) :
c . managedImageStorageAccountType = compute . StorageAccountTypesPremiumLRS
2017-08-13 04:45:04 -04:00
default :
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "The managed_image_storage_account_type %q is invalid" , c . ManagedImageStorageAccountType ) )
}
2018-11-30 15:32:41 -05:00
switch c . DiskCachingType {
case string ( compute . CachingTypesNone ) :
c . diskCachingType = compute . CachingTypesNone
case string ( compute . CachingTypesReadOnly ) :
c . diskCachingType = compute . CachingTypesReadOnly
case "" , string ( compute . CachingTypesReadWrite ) :
c . diskCachingType = compute . CachingTypesReadWrite
default :
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "The disk_caching_type %q is invalid" , c . DiskCachingType ) )
}
2016-03-04 05:14:55 -05:00
}
2017-11-17 12:49:23 -05:00
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
}
2018-11-06 14:17:03 -05:00
func assertManagedImageOSDiskSnapshotName ( name , setting string ) ( bool , error ) {
2018-11-07 17:23:22 -05:00
if ! isValidAzureName ( reSnapshotName , name ) {
2018-11-05 18:48:22 -05:00
return false , fmt . Errorf ( "The setting %s must only contain characters from a-z, A-Z, 0-9 and _ and the maximum length is 80 characters" , setting )
}
return true , nil
}
2018-11-06 14:17:03 -05:00
func assertManagedImageDataDiskSnapshotName ( name , setting string ) ( bool , error ) {
2018-11-07 17:23:22 -05:00
if ! isValidAzureName ( reSnapshotPrefix , name ) {
2018-11-06 14:17:03 -05:00
return false , fmt . Errorf ( "The setting %s must only contain characters from a-z, A-Z, 0-9 and _ and the maximum length (excluding the prefix) is 60 characters" , setting )
}
return true , nil
}
2017-11-17 12:49:23 -05:00
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 , "-" )
}
2019-02-15 10:24:19 -05:00
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 ) )
}
}