packer-cn/builder/azure/common/template/template_builder.go
Christopher Boumenot c7d8f4e150 azure: configuration for disk caching
Export a configuration knob to change the disk caching setting. The
default value remains ReadWrite.  This seems the most appropriate value
given Packer.  Certain disk sizes require that disk caching be disable,
and this knob allows the user to do just that.
2018-11-30 14:14:43 -08:00

627 lines
17 KiB
Go

package template
import (
"encoding/json"
"fmt"
"strings"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
"github.com/Azure/go-autorest/autorest/to"
)
const (
jsonPrefix = ""
jsonIndent = " "
resourceKeyVaults = "Microsoft.KeyVault/vaults"
resourceNetworkInterfaces = "Microsoft.Network/networkInterfaces"
resourcePublicIPAddresses = "Microsoft.Network/publicIPAddresses"
resourceVirtualMachine = "Microsoft.Compute/virtualMachines"
resourceVirtualNetworks = "Microsoft.Network/virtualNetworks"
variableSshKeyPath = "sshKeyPath"
)
type TemplateBuilder struct {
template *Template
osType compute.OperatingSystemTypes
}
func NewTemplateBuilder(template string) (*TemplateBuilder, error) {
var t Template
err := json.Unmarshal([]byte(template), &t)
if err != nil {
return nil, err
}
return &TemplateBuilder{
template: &t,
}, nil
}
func (s *TemplateBuilder) BuildLinux(sshAuthorizedKey string) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.OsProfile
profile.LinuxConfiguration = &compute.LinuxConfiguration{
SSH: &compute.SSHConfiguration{
PublicKeys: &[]compute.SSHPublicKey{
{
Path: to.StringPtr(s.toVariable(variableSshKeyPath)),
KeyData: to.StringPtr(sshAuthorizedKey),
},
},
},
}
s.osType = compute.Linux
return nil
}
func (s *TemplateBuilder) BuildWindows(keyVaultName, winRMCertificateUrl string) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.OsProfile
profile.Secrets = &[]compute.VaultSecretGroup{
{
SourceVault: &compute.SubResource{
ID: to.StringPtr(s.toResourceID(resourceKeyVaults, keyVaultName)),
},
VaultCertificates: &[]compute.VaultCertificate{
{
CertificateStore: to.StringPtr("My"),
CertificateURL: to.StringPtr(winRMCertificateUrl),
},
},
},
}
profile.WindowsConfiguration = &compute.WindowsConfiguration{
ProvisionVMAgent: to.BoolPtr(true),
WinRM: &compute.WinRMConfiguration{
Listeners: &[]compute.WinRMListener{
{
Protocol: "https",
CertificateURL: to.StringPtr(winRMCertificateUrl),
},
},
},
}
s.osType = compute.Windows
return nil
}
func (s *TemplateBuilder) SetManagedDiskUrl(managedImageId string, storageAccountType compute.StorageAccountTypes, cachingType compute.CachingTypes) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.StorageProfile
profile.ImageReference = &compute.ImageReference{
ID: &managedImageId,
}
profile.OsDisk.OsType = s.osType
profile.OsDisk.CreateOption = compute.DiskCreateOptionTypesFromImage
profile.OsDisk.Vhd = nil
profile.OsDisk.Caching = cachingType
profile.OsDisk.ManagedDisk = &compute.ManagedDiskParameters{
StorageAccountType: storageAccountType,
}
return nil
}
func (s *TemplateBuilder) SetManagedMarketplaceImage(location, publisher, offer, sku, version, imageID string, storageAccountType compute.StorageAccountTypes, cachingType compute.CachingTypes) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.StorageProfile
profile.ImageReference = &compute.ImageReference{
Publisher: &publisher,
Offer: &offer,
Sku: &sku,
Version: &version,
}
profile.OsDisk.OsType = s.osType
profile.OsDisk.CreateOption = compute.DiskCreateOptionTypesFromImage
profile.OsDisk.Vhd = nil
profile.OsDisk.Caching = cachingType
profile.OsDisk.ManagedDisk = &compute.ManagedDiskParameters{
StorageAccountType: storageAccountType,
}
return nil
}
func (s *TemplateBuilder) SetSharedGalleryImage(location, imageID string, cachingType compute.CachingTypes) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
s.setVariable("apiVersion", "2018-04-01") // Required for Shared Image Gallery
profile := resource.Properties.StorageProfile
profile.ImageReference = &compute.ImageReference{ID: &imageID}
profile.OsDisk.OsType = s.osType
profile.OsDisk.Vhd = nil
profile.OsDisk.Caching = cachingType
return nil
}
func (s *TemplateBuilder) SetMarketPlaceImage(publisher, offer, sku, version string, cachingType compute.CachingTypes) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.StorageProfile
profile.OsDisk.Caching = cachingType
profile.ImageReference = &compute.ImageReference{
Publisher: to.StringPtr(publisher),
Offer: to.StringPtr(offer),
Sku: to.StringPtr(sku),
Version: to.StringPtr(version),
}
return nil
}
func (s *TemplateBuilder) SetImageUrl(imageUrl string, osType compute.OperatingSystemTypes, cachingType compute.CachingTypes) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.StorageProfile
profile.OsDisk.OsType = osType
profile.OsDisk.Caching = cachingType
profile.OsDisk.Image = &compute.VirtualHardDisk{
URI: to.StringPtr(imageUrl),
}
return nil
}
func (s *TemplateBuilder) SetPlanInfo(name, product, publisher, promotionCode string) error {
var promotionCodeVal *string = nil
if promotionCode != "" {
promotionCodeVal = to.StringPtr(promotionCode)
}
for i, x := range *s.template.Resources {
if strings.EqualFold(*x.Type, resourceVirtualMachine) {
(*s.template.Resources)[i].Plan = &Plan{
Name: to.StringPtr(name),
Product: to.StringPtr(product),
Publisher: to.StringPtr(publisher),
PromotionCode: promotionCodeVal,
}
}
}
return nil
}
func (s *TemplateBuilder) SetOSDiskSizeGB(diskSizeGB int32) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.StorageProfile
profile.OsDisk.DiskSizeGB = to.Int32Ptr(diskSizeGB)
return nil
}
func (s *TemplateBuilder) SetAdditionalDisks(diskSizeGB []int32, isManaged bool, cachingType compute.CachingTypes) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.StorageProfile
dataDisks := make([]DataDiskUnion, len(diskSizeGB))
for i, additionalSize := range diskSizeGB {
dataDisks[i].DiskSizeGB = to.Int32Ptr(additionalSize)
dataDisks[i].Lun = to.IntPtr(i)
dataDisks[i].Name = to.StringPtr(fmt.Sprintf("datadisk-%d", i+1))
dataDisks[i].CreateOption = "Empty"
dataDisks[i].Caching = cachingType
if isManaged {
dataDisks[i].Vhd = nil
dataDisks[i].ManagedDisk = profile.OsDisk.ManagedDisk
} else {
dataDisks[i].Vhd = &compute.VirtualHardDisk{
URI: to.StringPtr(fmt.Sprintf("[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/datadisk-', '%d','.vhd')]", i+1)),
}
dataDisks[i].ManagedDisk = nil
}
}
profile.DataDisks = &dataDisks
return nil
}
func (s *TemplateBuilder) SetCustomData(customData string) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
}
profile := resource.Properties.OsProfile
profile.CustomData = to.StringPtr(customData)
return nil
}
func (s *TemplateBuilder) SetVirtualNetwork(virtualNetworkResourceGroup, virtualNetworkName, subnetName string) error {
s.setVariable("virtualNetworkResourceGroup", virtualNetworkResourceGroup)
s.setVariable("virtualNetworkName", virtualNetworkName)
s.setVariable("subnetName", subnetName)
s.deleteResourceByType(resourceVirtualNetworks)
s.deleteResourceByType(resourcePublicIPAddresses)
resource, err := s.getResourceByType(resourceNetworkInterfaces)
if err != nil {
return err
}
s.deleteResourceDependency(resource, func(s string) bool {
return strings.Contains(s, "Microsoft.Network/virtualNetworks") ||
strings.Contains(s, "Microsoft.Network/publicIPAddresses")
})
(*resource.Properties.IPConfigurations)[0].PublicIPAddress = nil
return nil
}
func (s *TemplateBuilder) SetPrivateVirtualNetworkWithPublicIp(virtualNetworkResourceGroup, virtualNetworkName, subnetName string) error {
s.setVariable("virtualNetworkResourceGroup", virtualNetworkResourceGroup)
s.setVariable("virtualNetworkName", virtualNetworkName)
s.setVariable("subnetName", subnetName)
s.deleteResourceByType(resourceVirtualNetworks)
resource, err := s.getResourceByType(resourceNetworkInterfaces)
if err != nil {
return err
}
s.deleteResourceDependency(resource, func(s string) bool {
return strings.Contains(s, "Microsoft.Network/virtualNetworks")
})
return nil
}
func (s *TemplateBuilder) SetTags(tags *map[string]*string) error {
if tags == nil || len(*tags) == 0 {
return nil
}
for i := range *s.template.Resources {
(*s.template.Resources)[i].Tags = tags
}
return nil
}
func (s *TemplateBuilder) ToJSON() (*string, error) {
bs, err := json.MarshalIndent(s.template, jsonPrefix, jsonIndent)
if err != nil {
return nil, err
}
return to.StringPtr(string(bs)), err
}
func (s *TemplateBuilder) getResourceByType(t string) (*Resource, error) {
for _, x := range *s.template.Resources {
if strings.EqualFold(*x.Type, t) {
return &x, nil
}
}
return nil, fmt.Errorf("template: could not find a resource of type %s", t)
}
func (s *TemplateBuilder) getResourceByType2(t string) (**Resource, error) {
for _, x := range *s.template.Resources {
if strings.EqualFold(*x.Type, t) {
p := &x
return &p, nil
}
}
return nil, fmt.Errorf("template: could not find a resource of type %s", t)
}
func (s *TemplateBuilder) setVariable(name string, value string) {
(*s.template.Variables)[name] = value
}
func (s *TemplateBuilder) toKeyVaultID(name string) string {
return s.toResourceID(resourceKeyVaults, name)
}
func (s *TemplateBuilder) toResourceID(id, name string) string {
return fmt.Sprintf("[resourceId(resourceGroup().name, '%s', '%s')]", id, name)
}
func (s *TemplateBuilder) toVariable(name string) string {
return fmt.Sprintf("[variables('%s')]", name)
}
func (s *TemplateBuilder) deleteResourceByType(resourceType string) {
resources := make([]Resource, 0)
for _, resource := range *s.template.Resources {
if *resource.Type == resourceType {
continue
}
resources = append(resources, resource)
}
s.template.Resources = &resources
}
func (s *TemplateBuilder) deleteResourceDependency(resource *Resource, predicate func(string) bool) {
deps := make([]string, 0)
for _, dep := range *resource.DependsOn {
if !predicate(dep) {
deps = append(deps, dep)
}
}
*resource.DependsOn = deps
}
// See https://github.com/Azure/azure-quickstart-templates for a extensive list of templates.
// Template to deploy a KeyVault.
//
// This template is still hard-coded unlike the ARM templates used for VMs for
// a couple of reasons.
//
// 1. The SDK defines no types for a Key Vault
// 2. The Key Vault template is relatively simple, and is static.
//
const KeyVault = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"keyVaultName": {
"type": "string"
},
"keyVaultSecretValue": {
"type": "securestring"
},
"objectId": {
"type": "string"
},
"tenantId": {
"type": "string"
}
},
"variables": {
"apiVersion": "2015-06-01",
"location": "[resourceGroup().location]",
"keyVaultSecretName": "packerKeyVaultSecret"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.KeyVault/vaults",
"name": "[parameters('keyVaultName')]",
"location": "[variables('location')]",
"properties": {
"enabledForDeployment": "true",
"enabledForTemplateDeployment": "true",
"tenantId": "[parameters('tenantId')]",
"accessPolicies": [
{
"tenantId": "[parameters('tenantId')]",
"objectId": "[parameters('objectId')]",
"permissions": {
"keys": [ "all" ],
"secrets": [ "all" ]
}
}
],
"sku": {
"name": "standard",
"family": "A"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "secrets",
"name": "[variables('keyVaultSecretName')]",
"dependsOn": [
"[concat('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]"
],
"properties": {
"value": "[parameters('keyVaultSecretValue')]"
}
}
]
}
]
}`
const BasicTemplate = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminUsername": {
"type": "string"
},
"adminPassword": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"nicName": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"publicIPAddressName": {
"type": "string"
},
"subnetName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"virtualNetworkName": {
"type": "string"
},
"vmSize": {
"type": "string"
},
"vmName": {
"type": "string"
}
},
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2017-03-30",
"managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01",
"publicIPAddressApiVersion": "2017-04-01",
"virtualNetworksApiVersion": "2017-04-01",
"location": "[resourceGroup().location]",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetName": "[parameters('subnetName')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "[parameters('virtualNetworkName')]",
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
},
"resources": [
{
"apiVersion": "[variables('publicIPAddressApiVersion')]",
"type": "Microsoft.Network/publicIPAddresses",
"name": "[parameters('publicIPAddressName')]",
"location": "[variables('location')]",
"properties": {
"publicIPAllocationMethod": "[variables('publicIPAddressType')]",
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
}
}
},
{
"apiVersion": "[variables('virtualNetworksApiVersion')]",
"type": "Microsoft.Network/virtualNetworks",
"name": "[variables('virtualNetworkName')]",
"location": "[variables('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]"
}
}
]
}
},
{
"apiVersion": "[variables('networkInterfacesApiVersion')]",
"type": "Microsoft.Network/networkInterfaces",
"name": "[parameters('nicName')]",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Compute/virtualMachines",
"name": "[parameters('vmName')]",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
],
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]"
},
"storageProfile": {
"osDisk": {
"name": "[parameters('osDiskName')]",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
},
"caching": "ReadWrite",
"createOption": "FromImage"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
}
]
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
}
}
}
]
}`