768 lines
29 KiB
Go
768 lines
29 KiB
Go
//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-plugin-sdk/common"
|
|
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
"github.com/hashicorp/packer/builder/azure/pkcs12"
|
|
|
|
"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}(?<![-.])$
|
|
//
|
|
// This is not an exhaustive match, but it should be extremely close.
|
|
validResourceGroupNameRe = `^[^_\W][\w-._\(\)]{0,89}$`
|
|
validManagedDiskName = `^[^_\W][\w-._)]{0,79}$`
|
|
)
|
|
|
|
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}$`)
|
|
reManagedDiskName = regexp.MustCompile(validManagedDiskName)
|
|
reResourceGroupName = regexp.MustCompile(validResourceGroupNameRe)
|
|
)
|
|
|
|
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"`
|
|
}
|
|
|
|
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"`
|
|
}
|
|
|
|
type DtlArtifact struct {
|
|
ArtifactName string `mapstructure:"artifact_name"`
|
|
RepositoryName string `mapstructure:"repository_name"`
|
|
ArtifactId string `mapstructure:"artifact_id"`
|
|
Parameters []ArtifactParameter `mapstructure:"parameters"`
|
|
}
|
|
|
|
type ArtifactParameter struct {
|
|
Name string `mapstructure:"name"`
|
|
Value string `mapstructure:"value"`
|
|
Type string `mapstructure:"type"`
|
|
}
|
|
|
|
type Config struct {
|
|
common.PackerConfig `mapstructure:",squash"`
|
|
|
|
// Authentication via OAUTH
|
|
ClientConfig client.Config `mapstructure:",squash"`
|
|
|
|
// Capture
|
|
CaptureNamePrefix string `mapstructure:"capture_name_prefix"`
|
|
CaptureContainerName string `mapstructure:"capture_container_name"`
|
|
|
|
// Use a [Shared Gallery
|
|
// image](https://azure.microsoft.com/en-us/blog/announcing-the-public-preview-of-shared-image-gallery/)
|
|
// as the source for this build. *VHD targets are incompatible with this
|
|
// build type* - the target must be a *Managed Image*.
|
|
//
|
|
// ```json
|
|
// "shared_image_gallery": {
|
|
// "subscription": "00000000-0000-0000-0000-00000000000",
|
|
// "resource_group": "ResourceGroup",
|
|
// "gallery_name": "GalleryName",
|
|
// "image_name": "ImageName",
|
|
// "image_version": "1.0.0"
|
|
// }
|
|
// "managed_image_name": "TargetImageName",
|
|
// "managed_image_resource_group_name": "TargetResourceGroup"
|
|
// ```
|
|
SharedGallery SharedImageGallery `mapstructure:"shared_image_gallery"`
|
|
|
|
// The name of the Shared Image Gallery under which the managed image will be published as Shared Gallery Image version.
|
|
//
|
|
// Following is an example.
|
|
//
|
|
// ```json
|
|
// "shared_image_gallery_destination": {
|
|
// "resource_group": "ResourceGroup",
|
|
// "gallery_name": "GalleryName",
|
|
// "image_name": "ImageName",
|
|
// "image_version": "1.0.0",
|
|
// "replication_regions": ["regionA", "regionB", "regionC"]
|
|
// }
|
|
// "managed_image_name": "TargetImageName",
|
|
// "managed_image_resource_group_name": "TargetResourceGroup"
|
|
// ```
|
|
SharedGalleryDestination SharedImageGalleryDestination `mapstructure:"shared_image_gallery_destination"`
|
|
|
|
// How long to wait for an image to be published to the shared image
|
|
// gallery before timing out. If your Packer build is failing on the
|
|
// Publishing to Shared Image Gallery step with the error `Original Error:
|
|
// context deadline exceeded`, but the image is present when you check your
|
|
// Azure dashboard, then you probably need to increase this timeout from
|
|
// its default of "60m" (valid time units include `s` for seconds, `m` for
|
|
// minutes, and `h` for hours.)
|
|
SharedGalleryTimeout time.Duration `mapstructure:"shared_image_gallery_timeout"`
|
|
|
|
// PublisherName for your base image. See
|
|
// [documentation](https://docs.microsoft.com/en-us/cli/azure/vm/image)
|
|
// for details.
|
|
//
|
|
// CLI example `az vm image list-publishers --location westus`
|
|
ImagePublisher string `mapstructure:"image_publisher"`
|
|
// Offer for your base image. See
|
|
// [documentation](https://docs.microsoft.com/en-us/cli/azure/vm/image)
|
|
// for details.
|
|
//
|
|
// CLI example
|
|
// `az vm image list-offers --location westus --publisher Canonical`
|
|
ImageOffer string `mapstructure:"image_offer"`
|
|
// SKU for your base image. See
|
|
// [documentation](https://docs.microsoft.com/en-us/cli/azure/vm/image)
|
|
// for details.
|
|
//
|
|
// CLI example
|
|
// `az vm image list-skus --location westus --publisher Canonical --offer UbuntuServer`
|
|
ImageSku string `mapstructure:"image_sku"`
|
|
// Specify a specific version of an OS to boot from.
|
|
// Defaults to `latest`. There may be a difference in versions available
|
|
// across regions due to image synchronization latency. To ensure a consistent
|
|
// version across regions set this value to one that is available in all
|
|
// regions where you are deploying.
|
|
//
|
|
// CLI example
|
|
// `az vm image list --location westus --publisher Canonical --offer UbuntuServer --sku 16.04.0-LTS --all`
|
|
ImageVersion string `mapstructure:"image_version"`
|
|
// Specify a custom VHD to use. If this value is set, do
|
|
// not set image_publisher, image_offer, image_sku, or image_version.
|
|
ImageUrl string `mapstructure:"image_url"`
|
|
|
|
// Specify the source managed image's resource group used to use. If this
|
|
// value is set, do not set image\_publisher, image\_offer, image\_sku, or
|
|
// image\_version. If this value is set, the value
|
|
// `custom_managed_image_name` must also be set. See
|
|
// [documentation](https://docs.microsoft.com/en-us/azure/storage/storage-managed-disks-overview#images)
|
|
// to learn more about managed images.
|
|
CustomManagedImageResourceGroupName string `mapstructure:"custom_managed_image_resource_group_name"`
|
|
// Specify the source managed image's name to use. If this value is set, do
|
|
// not set image\_publisher, image\_offer, image\_sku, or image\_version.
|
|
// If this value is set, the value
|
|
// `custom_managed_image_resource_group_name` must also be set. See
|
|
// [documentation](https://docs.microsoft.com/en-us/azure/storage/storage-managed-disks-overview#images)
|
|
// to learn more about managed images.
|
|
CustomManagedImageName string `mapstructure:"custom_managed_image_name"`
|
|
customManagedImageID string
|
|
|
|
Location string `mapstructure:"location"`
|
|
// Size of the VM used for building. This can be changed when you deploy a
|
|
// VM from your VHD. See
|
|
// [pricing](https://azure.microsoft.com/en-us/pricing/details/virtual-machines/)
|
|
// information. Defaults to `Standard_A1`.
|
|
//
|
|
// CLI example `az vm list-sizes --location westus`
|
|
VMSize string `mapstructure:"vm_size"`
|
|
// Specify the managed image resource group name where the result of the
|
|
// Packer build will be saved. The resource group must already exist. If
|
|
// this value is set, the value managed_image_name must also be set. See
|
|
// documentation to learn more about managed images.
|
|
ManagedImageResourceGroupName string `mapstructure:"managed_image_resource_group_name"`
|
|
// Specify the managed image name where the result of the Packer build will
|
|
// be saved. The image name must not exist ahead of time, and will not be
|
|
// overwritten. If this value is set, the value
|
|
// managed_image_resource_group_name must also be set. See documentation to
|
|
// learn more about managed images.
|
|
ManagedImageName string `mapstructure:"managed_image_name"`
|
|
// Specify the storage account
|
|
// type for a managed image. Valid values are Standard_LRS and Premium_LRS.
|
|
// The default is Standard_LRS.
|
|
ManagedImageStorageAccountType string `mapstructure:"managed_image_storage_account_type" required:"false"`
|
|
managedImageStorageAccountType compute.StorageAccountTypes
|
|
|
|
// the user can define up to 15
|
|
// tags. Tag names cannot exceed 512 characters, and tag values cannot exceed
|
|
// 256 characters. Tags are applied to every resource deployed by a Packer
|
|
// build, i.e. Resource Group, VM, NIC, VNET, Public IP, KeyVault, etc.
|
|
AzureTags map[string]*string `mapstructure:"azure_tags" required:"false"`
|
|
|
|
// Used for creating images from Marketplace images. Please refer to
|
|
// [Deploy an image with Marketplace
|
|
// terms](https://aka.ms/azuremarketplaceapideployment) for more details.
|
|
// Not all Marketplace images support programmatic deployment, and support
|
|
// is controlled by the image publisher.
|
|
// Plan_id is a string with unique identifier for the plan associated with images.
|
|
// Ex plan_id="1-12ab"
|
|
PlanID string `mapstructure:"plan_id" required:"false"`
|
|
|
|
// The default PollingDuration for azure is 15mins, this property will override
|
|
// that value. See [Azure DefaultPollingDuration](https://godoc.org/github.com/Azure/go-autorest/autorest#pkg-constants)
|
|
// If your Packer build is failing on the
|
|
// ARM deployment step with the error `Original Error:
|
|
// context deadline exceeded`, then you probably need to increase this timeout from
|
|
// its default of "15m" (valid time units include `s` for seconds, `m` for
|
|
// minutes, and `h` for hours.)
|
|
PollingDurationTimeout time.Duration `mapstructure:"polling_duration_timeout" required:"false"`
|
|
// If either Linux or Windows is specified Packer will
|
|
// automatically configure authentication credentials for the provisioned
|
|
// machine. For Linux this configures an SSH authorized key. For Windows
|
|
// this configures a WinRM certificate.
|
|
OSType string `mapstructure:"os_type" required:"false"`
|
|
// Specify the size of the OS disk in GB
|
|
// (gigabytes). Values of zero or less than zero are ignored.
|
|
OSDiskSizeGB int32 `mapstructure:"os_disk_size_gb" required:"false"`
|
|
|
|
// The size(s) of any additional hard disks for the VM in gigabytes. If
|
|
// this is not specified then the VM will only contain an OS disk. The
|
|
// number of additional disks and maximum size of a disk depends on the
|
|
// configuration of your VM. See
|
|
// [Windows](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/about-disks-and-vhds)
|
|
// or
|
|
// [Linux](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/about-disks-and-vhds)
|
|
// for more information.
|
|
//
|
|
|
|
// For Managed build the final artifacts are included in the managed image.
|
|
// The additional disk will have the same storage account type as the OS
|
|
// disk, as specified with the `managed_image_storage_account_type`
|
|
// setting.
|
|
AdditionalDiskSize []int32 `mapstructure:"disk_additional_size" required:"false"`
|
|
// Specify the disk caching type. Valid values
|
|
// are None, ReadOnly, and ReadWrite. The default value is ReadWrite.
|
|
DiskCachingType string `mapstructure:"disk_caching_type" required:"false"`
|
|
diskCachingType compute.CachingTypes
|
|
|
|
// DTL values
|
|
StorageType string `mapstructure:"storage_type"`
|
|
LabVirtualNetworkName string `mapstructure:"lab_virtual_network_name"`
|
|
LabName string `mapstructure:"lab_name"`
|
|
LabSubnetName string `mapstructure:"lab_subnet_name"`
|
|
LabResourceGroupName string `mapstructure:"lab_resource_group_name"`
|
|
|
|
DtlArtifacts []DtlArtifact `mapstructure:"dtl_artifacts"`
|
|
VMName string `mapstructure:"vm_name"`
|
|
|
|
// Runtime Values
|
|
UserName string
|
|
Password string
|
|
tmpAdminPassword string
|
|
tmpCertificatePassword string
|
|
tmpResourceGroupName string
|
|
tmpComputeName string
|
|
tmpNicName string
|
|
tmpPublicIPAddressName string
|
|
tmpDeploymentName string
|
|
tmpKeyVaultName string
|
|
tmpOSDiskName string
|
|
tmpSubnetName string
|
|
tmpVirtualNetworkName string
|
|
VMCreationResourceGroup string
|
|
tmpFQDN string
|
|
|
|
// Authentication with the VM via SSH
|
|
sshAuthorizedKey string
|
|
|
|
// Authentication with the VM via WinRM
|
|
winrmCertificate string
|
|
winrmPassword string
|
|
|
|
Comm communicator.Config `mapstructure:",squash"`
|
|
ctx interpolate.Context
|
|
}
|
|
|
|
type keyVaultCertificate struct {
|
|
Data string `json:"data"`
|
|
DataType string `json:"dataType"`
|
|
Password string `json:"password,omitempty"`
|
|
}
|
|
|
|
func (c *Config) isManagedImage() bool {
|
|
return c.ManagedImageName != ""
|
|
}
|
|
|
|
func (c *Config) toVirtualMachineCaptureParameters() *compute.VirtualMachineCaptureParameters {
|
|
return &compute.VirtualMachineCaptureParameters{
|
|
DestinationContainerName: &c.CaptureContainerName,
|
|
VhdPrefix: &c.CaptureNamePrefix,
|
|
OverwriteVhds: to.BoolPtr(false),
|
|
}
|
|
}
|
|
|
|
func (c *Config) createCertificate() (string, string, error) {
|
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
err = fmt.Errorf("Failed to Generate Private Key: %s", err)
|
|
return "", "", err
|
|
}
|
|
|
|
host := fmt.Sprintf("%s.centralus.cloudapp.azure.com", c.tmpComputeName)
|
|
notBefore := time.Now()
|
|
notAfter := notBefore.Add(24 * time.Hour)
|
|
|
|
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
|
if err != nil {
|
|
err = fmt.Errorf("Failed to Generate Serial Number: %v", err)
|
|
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
|
|
}
|
|
|
|
certifcatePassowrd := base64.StdEncoding.EncodeToString([]byte(c.tmpCertificatePassword))
|
|
return base64.StdEncoding.EncodeToString(bytes), certifcatePassowrd, nil
|
|
}
|
|
|
|
func newConfig(raws ...interface{}) (*Config, []string, error) {
|
|
var c Config
|
|
c.ctx.Funcs = TemplateFuncs
|
|
err := config.Decode(&c, &config.DecodeOpts{
|
|
PluginType: BuilderId,
|
|
Interpolate: true,
|
|
InterpolateContext: &c.ctx,
|
|
}, raws...)
|
|
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
provideDefaultValues(&c)
|
|
setRuntimeValues(&c)
|
|
setUserNamePassword(&c)
|
|
err = c.ClientConfig.SetDefaultValues()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// 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
|
|
// not specifically force the user to set a communicator.
|
|
if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "ssh") {
|
|
err = setSshValues(&c)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
//For DTL, communication is done by installing Mandatory public artifact "windows-winrm"
|
|
if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "winrm") {
|
|
err = setWinRMCertificate(&c)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
var errs *packersdk.MultiError
|
|
errs = packersdk.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
|
|
|
c.ClientConfig.Validate(errs)
|
|
|
|
assertRequiredParametersSet(&c, errs)
|
|
assertTagProperties(&c, errs)
|
|
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
|
|
}
|
|
|
|
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
|
|
packersdk.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 *packersdk.MultiError) {
|
|
if len(c.AzureTags) > 15 {
|
|
errs = packersdk.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 = packersdk.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 512 character limit", k, len(k)))
|
|
}
|
|
if len(*v) > 256 {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", *v, len(*v)))
|
|
}
|
|
}
|
|
}
|
|
|
|
func assertRequiredParametersSet(c *Config, errs *packersdk.MultiError) {
|
|
c.ClientConfig.Validate(errs)
|
|
|
|
/////////////////////////////////////////////
|
|
// Capture
|
|
if c.CaptureContainerName == "" && c.ManagedImageName == "" {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name or managed_image_name must be specified"))
|
|
}
|
|
|
|
if c.CaptureNamePrefix == "" && c.ManagedImageResourceGroupName == "" {
|
|
errs = packersdk.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 = packersdk.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 = packersdk.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must satisfy the regular expression %q.", reCaptureContainerName.String()))
|
|
}
|
|
|
|
if strings.HasSuffix(c.CaptureContainerName, "-") {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not end with a hyphen, e.g. '-'."))
|
|
}
|
|
|
|
if strings.Contains(c.CaptureContainerName, "--") {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not contain consecutive hyphens, e.g. '--'."))
|
|
}
|
|
|
|
if c.CaptureNamePrefix == "" {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must be specified"))
|
|
}
|
|
|
|
if !reCaptureNamePrefix.MatchString(c.CaptureNamePrefix) {
|
|
errs = packersdk.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 = packersdk.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must not end with a hyphen or period."))
|
|
}
|
|
}
|
|
|
|
if c.LabResourceGroupName == "" {
|
|
errs = packersdk.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 = packersdk.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 = packersdk.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 = packersdk.MultiErrorAppend(errs, fmt.Errorf("A shared_image_gallery.subscription must be specified"))
|
|
}
|
|
if c.SharedGallery.ResourceGroup == "" {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("A shared_image_gallery.resource_group must be specified"))
|
|
}
|
|
if c.SharedGallery.ImageName == "" {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("A shared_image_gallery.image_name must be specified"))
|
|
}
|
|
if c.CaptureContainerName != "" {
|
|
errs = packersdk.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 = packersdk.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 = packersdk.MultiErrorAppend(errs, fmt.Errorf("An image_publisher must be specified"))
|
|
}
|
|
if c.ImageOffer == "" {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("An image_offer must be specified"))
|
|
}
|
|
if c.ImageSku == "" {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("An image_sku must be specified"))
|
|
}
|
|
} else if c.ImageUrl == "" && c.ImagePublisher == "" {
|
|
if c.CustomManagedImageResourceGroupName == "" {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("An custom_managed_image_resource_group_name must be specified"))
|
|
}
|
|
if c.CustomManagedImageName == "" {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("A custom_managed_image_name must be specified"))
|
|
}
|
|
if c.ManagedImageResourceGroupName == "" {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("An managed_image_resource_group_name must be specified"))
|
|
}
|
|
if c.ManagedImageName == "" {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("An managed_image_name must be specified"))
|
|
}
|
|
} else {
|
|
if c.ImagePublisher != "" || c.ImageOffer != "" || c.ImageSku != "" || c.ImageVersion != "" {
|
|
errs = packersdk.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 = packersdk.MultiErrorAppend(errs, err)
|
|
}
|
|
}
|
|
|
|
if c.ManagedImageName != "" {
|
|
if ok, err := assertManagedImageName(c.ManagedImageName, "managed_image_name"); !ok {
|
|
errs = packersdk.MultiErrorAppend(errs, err)
|
|
}
|
|
}
|
|
|
|
if c.LabVirtualNetworkName == "" && c.LabResourceGroupName != "" {
|
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("If lab_resource_group_name is specified, so must lab_virtual_network_name"))
|
|
}
|
|
if c.LabVirtualNetworkName == "" && c.LabSubnetName != "" {
|
|
errs = packersdk.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 = packersdk.MultiErrorAppend(errs, fmt.Errorf("An os_type must be specified"))
|
|
} else {
|
|
errs = packersdk.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 = packersdk.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))
|
|
}
|
|
}
|