azure: Support for a user define VNET.

Two new configuration options have been exposed to allow users to specify
an existing virtual network: virtual_network_name and
virtual_network_resource_group_name.

 * virtual_network_name: name of the virtual network to attach a Packer VM
 to.

 * virtual_network_resource_group_name: name of the resource group that
 contains the virtual network.  This value is optional.  If the value is
 not specified, the builder queries Azure for the appropriate value.  If
 the builder cannot disambiguate the value, a value must be provided for
 this setting.

 * virtual_network_subnet_name: name of the subnet attached to the virtual
 network.  This value is optional.  If the value is not specified, the
 builder queries Azure for the appropriate value.  If the builder cannot
 disambiguate the value, a value must be provided for this setting.
This commit is contained in:
Christopher Boumenot 2016-06-30 16:51:52 -07:00
parent 95cffcae78
commit 871ca8c3d9
26 changed files with 1023 additions and 52 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@
.idea
test/.env
*~
*.received.*
website/.bundle
website/vendor

View File

@ -0,0 +1,120 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [],
"location": "[variables('location')]",
"name": "[variables('nicName')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"location": "[variables('location')]",
"name": "[parameters('vmName')]",
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"osProfile": {
"adminPassword": "[parameters('adminPassword')]",
"adminUsername": "[parameters('adminUsername')]",
"computerName": "[parameters('vmName')]",
"linuxConfiguration": {
"ssh": {
"publicKeys": [
{
"keyData": "",
"path": "[variables('sshKeyPath')]"
}
]
}
}
},
"storageProfile": {
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"image": {
"uri": "https://localhost/custom.vhd"
},
"name": "osdisk",
"osType": "Linux",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
}
}
}
},
"type": "Microsoft.Compute/virtualMachines"
}
],
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "ignore",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "ignore",
"virtualNetworkResourceGroup": "ignore",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}

View File

@ -36,6 +36,9 @@ type AzureClient struct {
resources.DeploymentsClient
resources.GroupsClient
network.PublicIPAddressesClient
network.InterfacesClient
network.SubnetsClient
network.VirtualNetworksClient
compute.VirtualMachinesClient
common.VaultClient
armStorage.AccountsClient
@ -122,6 +125,24 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
azureClient.GroupsClient.ResponseInspector = byInspecting(maxlen)
azureClient.GroupsClient.UserAgent += packerUserAgent
azureClient.InterfacesClient = network.NewInterfacesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.InterfacesClient.Authorizer = servicePrincipalToken
azureClient.InterfacesClient.RequestInspector = withInspection(maxlen)
azureClient.InterfacesClient.ResponseInspector = byInspecting(maxlen)
azureClient.InterfacesClient.UserAgent += packerUserAgent
azureClient.SubnetsClient = network.NewSubnetsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.SubnetsClient.Authorizer = servicePrincipalToken
azureClient.SubnetsClient.RequestInspector = withInspection(maxlen)
azureClient.SubnetsClient.ResponseInspector = byInspecting(maxlen)
azureClient.SubnetsClient.UserAgent += packerUserAgent
azureClient.VirtualNetworksClient = network.NewVirtualNetworksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.VirtualNetworksClient.Authorizer = servicePrincipalToken
azureClient.VirtualNetworksClient.RequestInspector = withInspection(maxlen)
azureClient.VirtualNetworksClient.ResponseInspector = byInspecting(maxlen)
azureClient.VirtualNetworksClient.UserAgent += packerUserAgent
azureClient.PublicIPAddressesClient = network.NewPublicIPAddressesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.PublicIPAddressesClient.Authorizer = servicePrincipalToken
azureClient.PublicIPAddressesClient.RequestInspector = withInspection(maxlen)

View File

@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"log"
"strings"
"time"
packerAzureCommon "github.com/mitchellh/packer/builder/azure/common"
@ -20,7 +21,6 @@ import (
packerCommon "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/packer"
"strings"
)
type Builder struct {
@ -30,6 +30,7 @@ type Builder struct {
}
const (
DefaultNicName = "packerNic"
DefaultPublicIPAddressName = "packerPublicIP"
DefaultSasBlobContainer = "system/Microsoft.Compute"
DefaultSasBlobPermission = "r"
@ -82,11 +83,21 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
return nil, err
}
resolver := newResourceResolver(azureClient)
if err := resolver.Resolve(b.config); err != nil {
return nil, err
}
b.config.storageAccountBlobEndpoint, err = b.getBlobEndpoint(azureClient, b.config.ResourceGroupName, b.config.StorageAccount)
if err != nil {
return nil, err
}
endpointConnectType := PublicEndpoint
if b.isPrivateNetworkCommunication() {
endpointConnectType = PrivateEndpoint
}
b.setTemplateParameters(b.stateBag)
var steps []multistep.Step
@ -95,7 +106,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
NewStepCreateResourceGroup(azureClient, ui),
NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
NewStepDeployTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
NewStepGetIPAddress(azureClient, ui),
NewStepGetIPAddress(azureClient, ui, endpointConnectType),
&communicator.StepConnectSSH{
Config: &b.config.Comm,
Host: lin.SSHHost,
@ -117,7 +128,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
NewStepSetCertificate(b.config, ui),
NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
NewStepDeployTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
NewStepGetIPAddress(azureClient, ui),
NewStepGetIPAddress(azureClient, ui, endpointConnectType),
&communicator.StepConnectWinRM{
Config: &b.config.Comm,
Host: func(stateBag multistep.StateBag) (string, error) {
@ -176,6 +187,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
return &Artifact{}, nil
}
func (b *Builder) isPrivateNetworkCommunication() bool {
return b.config.VirtualNetworkName != ""
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
@ -213,6 +228,7 @@ func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
stateBag.Put(constants.ArmKeyVaultName, b.config.tmpKeyVaultName)
stateBag.Put(constants.ArmLocation, b.config.Location)
stateBag.Put(constants.ArmNicName, DefaultNicName)
stateBag.Put(constants.ArmPublicIPAddressName, DefaultPublicIPAddressName)
stateBag.Put(constants.ArmResourceGroupName, b.config.tmpResourceGroupName)
stateBag.Put(constants.ArmStorageAccountName, b.config.StorageAccount)

View File

@ -22,6 +22,7 @@ func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
constants.ArmComputeName,
constants.ArmDeploymentName,
constants.ArmLocation,
constants.ArmNicName,
constants.ArmResourceGroupName,
constants.ArmStorageAccountName,
constants.ArmVirtualMachineCaptureParameters,

View File

@ -70,11 +70,14 @@ type Config struct {
VMSize string `mapstructure:"vm_size"`
// Deployment
ResourceGroupName string `mapstructure:"resource_group_name"`
StorageAccount string `mapstructure:"storage_account"`
storageAccountBlobEndpoint string
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
cloudEnvironment *azure.Environment
ResourceGroupName string `mapstructure:"resource_group_name"`
StorageAccount string `mapstructure:"storage_account"`
storageAccountBlobEndpoint string
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
cloudEnvironment *azure.Environment
VirtualNetworkName string `mapstructure:"virtual_network_name"`
VirtualNetworkSubnetName string `mapstructure:"virtual_network_subnet_name"`
VirtualNetworkResourceGroupName string `mapstructure:"virtual_network_resource_group_name"`
// OS
OSType string `mapstructure:"os_type"`
@ -447,6 +450,12 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
if c.ResourceGroupName == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A resource_group_name must be specified"))
}
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"))
}
/////////////////////////////////////////////
// OS

View File

@ -1,5 +1,12 @@
package arm
// Method to resolve information about the user so that a client can be
// constructed to communicated with Azure.
//
// The following data are resolved.
//
// 1. TenantID
import (
"github.com/Azure/go-autorest/autorest/azure"
"github.com/mitchellh/packer/builder/azure/common"
@ -11,7 +18,9 @@ type configRetriever struct {
}
func newConfigRetriever() configRetriever {
return configRetriever{common.FindTenantID}
return configRetriever{
common.FindTenantID,
}
}
func (cr configRetriever) FillParameters(c *Config) error {
@ -22,5 +31,6 @@ func (cr configRetriever) FillParameters(c *Config) error {
}
c.TenantID = tenantID
}
return nil
}

View File

@ -142,6 +142,78 @@ func TestConfigShouldRejectCustomImageAndMarketPlace(t *testing.T) {
}
}
func TestConfigVirtualNetworkNameIsOptional(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
"virtual_network_name": "MyVirtualNetwork",
}
c, _, _ := newConfig(config, getPackerConfiguration())
if c.VirtualNetworkName != "MyVirtualNetwork" {
t.Errorf("Expected Config to set virtual_network_name to MyVirtualNetwork, but got %q", c.VirtualNetworkName)
}
if c.VirtualNetworkResourceGroupName != "" {
t.Errorf("Expected Config to leave virtual_network_resource_group_name to '', but got %q", c.VirtualNetworkResourceGroupName)
}
if c.VirtualNetworkSubnetName != "" {
t.Errorf("Expected Config to leave virtual_network_subnet_name to '', but got %q", c.VirtualNetworkSubnetName)
}
}
// The user can pass the value virtual_network_resource_group_name to avoid the lookup of
// a virtual network's resource group, or to help with disambiguation. The value should
// only be set if virtual_network_name was set.
func TestConfigVirtualNetworkResourceGroupNameMustBeSetWithVirtualNetworkName(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
"virtual_network_resource_group_name": "MyVirtualNetworkRG",
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Error("Expected Config to reject virtual_network_resource_group_name, if virtual_network_name is not set.")
}
}
// The user can pass the value virtual_network_subnet_name to avoid the lookup of
// a virtual network subnet's name, or to help with disambiguation. The value should
// only be set if virtual_network_name was set.
func TestConfigVirtualNetworkSubnetNameMustBeSetWithVirtualNetworkName(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
"virtual_network_subnet_name": "MyVirtualNetworkRG",
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Error("Expected Config to reject virtual_network_subnet_name, if virtual_network_name is not set.")
}
}
func TestConfigShouldDefaultToPublicCloud(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())

View File

@ -0,0 +1,101 @@
package arm
// Code to resolve resources that are required by the API. These resources
// can most likely be resolved without asking the user, thereby reducing the
// amount of configuration they need to provide.
//
// Resource resolver differs from config retriever because resource resolver
// requires a client to communicate with the Azure API. A config retriever is
// used to determine values without use of a client.
import (
"fmt"
"strings"
)
type resourceResolver struct {
client *AzureClient
findVirtualNetworkResourceGroup func(*AzureClient, string) (string, error)
findVirtualNetworkSubnet func(*AzureClient, string, string) (string, error)
}
func newResourceResolver(client *AzureClient) *resourceResolver {
return &resourceResolver{
client: client,
findVirtualNetworkResourceGroup: findVirtualNetworkResourceGroup,
findVirtualNetworkSubnet: findVirtualNetworkSubnet,
}
}
func (s *resourceResolver) Resolve(c *Config) error {
if s.shouldResolveResourceGroup(c) {
resourceGroupName, err := s.findVirtualNetworkResourceGroup(s.client, c.VirtualNetworkName)
if err != nil {
return err
}
subnetName, err := s.findVirtualNetworkSubnet(s.client, resourceGroupName, c.VirtualNetworkName)
if err != nil {
return err
}
c.VirtualNetworkResourceGroupName = resourceGroupName
c.VirtualNetworkSubnetName = subnetName
}
return nil
}
func (s *resourceResolver) shouldResolveResourceGroup(c *Config) bool {
return c.VirtualNetworkName != "" && c.VirtualNetworkResourceGroupName == ""
}
func getResourceGroupNameFromId(id string) string {
// "/subscriptions/3f499422-dd76-4114-8859-86d526c9deb6/resourceGroups/packer-Resource-Group-yylnwsl30j/providers/...
xs := strings.Split(id, "/")
return xs[4]
}
func findVirtualNetworkResourceGroup(client *AzureClient, name string) (string, error) {
virtualNetworks, err := client.VirtualNetworksClient.ListAll()
if err != nil {
return "", err
}
resourceGroupNames := make([]string, 0)
for _, virtualNetwork := range *virtualNetworks.Value {
if strings.EqualFold(name, *virtualNetwork.Name) {
rgn := getResourceGroupNameFromId(*virtualNetwork.ID)
resourceGroupNames = append(resourceGroupNames, rgn)
}
}
if len(resourceGroupNames) == 0 {
return "", fmt.Errorf("Cannot find a resource group with a virtual network called %q", name)
}
if len(resourceGroupNames) > 1 {
return "", fmt.Errorf("Found multiple resource groups with a virtual network called %q, please use virtual_network_resource_group_name to disambiguate", name)
}
return resourceGroupNames[0], nil
}
func findVirtualNetworkSubnet(client *AzureClient, resourceGroupName string, name string) (string, error) {
subnets, err := client.SubnetsClient.List(resourceGroupName, name)
if err != nil {
return "", err
}
if len(*subnets.Value) == 0 {
return "", fmt.Errorf("Cannot find a subnet in the resource group %q associated with the virtual network called %q", resourceGroupName, name)
}
if len(*subnets.Value) > 1 {
return "", fmt.Errorf("Found multiple subnets in the resource group %q associated with the virtual network called %q, please use virtual_network_subnet_name to disambiguate", resourceGroupName, name)
}
subnet := (*subnets.Value)[0]
return *subnet.Name, nil
}

View File

@ -0,0 +1,76 @@
package arm
import (
"testing"
)
func TestResourceResolverIgnoresEmptyVirtualNetworkName(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
if c.VirtualNetworkName != "" {
t.Fatalf("Expected VirtualNetworkName to be empty by default")
}
sut := newTestResourceResolver()
sut.findVirtualNetworkResourceGroup = nil // assert that this is not even called
sut.Resolve(c)
if c.VirtualNetworkName != "" {
t.Fatalf("Expected VirtualNetworkName to be empty")
}
if c.VirtualNetworkResourceGroupName != "" {
t.Fatalf("Expected VirtualNetworkResourceGroupName to be empty")
}
}
// If the user fully specified the virtual network name and resource group then
// there is no need to do a lookup.
func TestResourceResolverIgnoresSetVirtualNetwork(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
c.VirtualNetworkName = "--virtual-network-name--"
c.VirtualNetworkResourceGroupName = "--virtual-network-resource-group-name--"
c.VirtualNetworkSubnetName = "--virtual-network-subnet-name--"
sut := newTestResourceResolver()
sut.findVirtualNetworkResourceGroup = nil // assert that this is not even called
sut.findVirtualNetworkSubnet = nil // assert that this is not even called
sut.Resolve(c)
if c.VirtualNetworkName != "--virtual-network-name--" {
t.Fatalf("Expected VirtualNetworkName to be --virtual-network-name--")
}
if c.VirtualNetworkResourceGroupName != "--virtual-network-resource-group-name--" {
t.Fatalf("Expected VirtualNetworkResourceGroupName to be --virtual-network-resource-group-name--")
}
if c.VirtualNetworkSubnetName != "--virtual-network-subnet-name--" {
t.Fatalf("Expected VirtualNetworkSubnetName to be --virtual-network-subnet-name--")
}
}
// If the user set virtual network name then the code should resolve virtual network
// resource group name.
func TestResourceResolverSetVirtualNetworkResourceGroupName(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
c.VirtualNetworkName = "--virtual-network-name--"
sut := newTestResourceResolver()
sut.Resolve(c)
if c.VirtualNetworkResourceGroupName != "findVirtualNetworkResourceGroup is mocked" {
t.Fatalf("Expected VirtualNetworkResourceGroupName to be 'findVirtualNetworkResourceGroup is mocked'")
}
if c.VirtualNetworkSubnetName != "findVirtualNetworkSubnet is mocked" {
t.Fatalf("Expected findVirtualNetworkSubnet to be 'findVirtualNetworkSubnet is mocked'")
}
}
func newTestResourceResolver() resourceResolver {
return resourceResolver{
client: nil,
findVirtualNetworkResourceGroup: func(*AzureClient, string) (string, error) {
return "findVirtualNetworkResourceGroup is mocked", nil
},
findVirtualNetworkSubnet: func(*AzureClient, string, string) (string, error) {
return "findVirtualNetworkSubnet is mocked", nil
},
}
}

View File

@ -11,43 +11,77 @@ import (
"github.com/mitchellh/packer/packer"
)
type EndpointType int
const (
PublicEndpoint EndpointType = iota
PrivateEndpoint
)
var (
EndpointCommunicationText = map[EndpointType]string{
PublicEndpoint: "PublicEndpoint",
PrivateEndpoint: "PrivateEndpoint",
}
)
type StepGetIPAddress struct {
client *AzureClient
get func(resourceGroupName string, ipAddressName string) (string, error)
say func(message string)
error func(e error)
client *AzureClient
endpoint EndpointType
get func(resourceGroupName string, ipAddressName string, interfaceName string) (string, error)
say func(message string)
error func(e error)
}
func NewStepGetIPAddress(client *AzureClient, ui packer.Ui) *StepGetIPAddress {
func NewStepGetIPAddress(client *AzureClient, ui packer.Ui, endpoint EndpointType) *StepGetIPAddress {
var step = &StepGetIPAddress{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
client: client,
endpoint: endpoint,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
}
switch endpoint {
case PrivateEndpoint:
step.get = step.getPrivateIP
case PublicEndpoint:
step.get = step.getPublicIP
}
step.get = step.getIPAddress
return step
}
func (s *StepGetIPAddress) getIPAddress(resourceGroupName string, ipAddressName string) (string, error) {
res, err := s.client.PublicIPAddressesClient.Get(resourceGroupName, ipAddressName, "")
func (s *StepGetIPAddress) getPrivateIP(resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
resp, err := s.client.InterfacesClient.Get(resourceGroupName, interfaceName, "")
if err != nil {
return "", nil
return "", err
}
return *res.Properties.IPAddress, nil
return *(*resp.Properties.IPConfigurations)[0].Properties.PrivateIPAddress, nil
}
func (s *StepGetIPAddress) getPublicIP(resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
resp, err := s.client.PublicIPAddressesClient.Get(resourceGroupName, ipAddressName, "")
if err != nil {
return "", err
}
return *resp.Properties.IPAddress, nil
}
func (s *StepGetIPAddress) Run(state multistep.StateBag) multistep.StepAction {
s.say("Getting the public IP address ...")
s.say("Getting the VM's IP address ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var ipAddressName = state.Get(constants.ArmPublicIPAddressName).(string)
var nicName = state.Get(constants.ArmNicName).(string)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> PublicIPAddressName : '%s'", ipAddressName))
s.say(fmt.Sprintf(" -> NicName : '%s'", nicName))
s.say(fmt.Sprintf(" -> Network Connection : '%s'", EndpointCommunicationText[s.endpoint]))
address, err := s.get(resourceGroupName, ipAddressName)
address, err := s.get(resourceGroupName, ipAddressName, nicName)
if err != nil {
state.Put(constants.Error, err)
s.error(err)
@ -55,8 +89,8 @@ func (s *StepGetIPAddress) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt
}
s.say(fmt.Sprintf(" -> Public IP : '%s'", address))
state.Put(constants.SSHHost, address)
s.say(fmt.Sprintf(" -> IP Address : '%s'", address))
return multistep.ActionContinue
}

View File

@ -13,9 +13,10 @@ import (
func TestStepGetIPAddressShouldFailIfGetFails(t *testing.T) {
var testSubject = &StepGetIPAddress{
get: func(string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
say: func(message string) {},
error: func(e error) {},
get: func(string, string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
endpoint: PublicEndpoint,
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepGetIPAddress()
@ -32,9 +33,10 @@ func TestStepGetIPAddressShouldFailIfGetFails(t *testing.T) {
func TestStepGetIPAddressShouldPassIfGetPasses(t *testing.T) {
var testSubject = &StepGetIPAddress{
get: func(string, string) (string, error) { return "", nil },
say: func(message string) {},
error: func(e error) {},
get: func(string, string, string) (string, error) { return "", nil },
endpoint: PublicEndpoint,
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepGetIPAddress()
@ -52,16 +54,19 @@ func TestStepGetIPAddressShouldPassIfGetPasses(t *testing.T) {
func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualIPAddressName string
var actualNicName string
var testSubject = &StepGetIPAddress{
get: func(resourceGroupName string, ipAddressName string) (string, error) {
get: func(resourceGroupName string, ipAddressName string, nicName string) (string, error) {
actualResourceGroupName = resourceGroupName
actualIPAddressName = ipAddressName
actualNicName = nicName
return "127.0.0.1", nil
},
say: func(message string) {},
error: func(e error) {},
endpoint: PublicEndpoint,
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepGetIPAddress()
@ -73,6 +78,7 @@ func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
var expectedIPAddressName = stateBag.Get(constants.ArmPublicIPAddressName).(string)
var expectedNicName = stateBag.Get(constants.ArmNicName).(string)
if actualIPAddressName != expectedIPAddressName {
t.Fatal("Expected StepGetIPAddress to source 'constants.ArmIPAddressName' from the state bag, but it did not.")
@ -82,6 +88,10 @@ func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
t.Fatal("Expected StepGetIPAddress to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualNicName != expectedNicName {
t.Fatalf("Expected StepGetIPAddress to source 'constants.ArmNetworkInterfaceName' from the state bag, but it did not.")
}
expectedIPAddress, ok := stateBag.GetOk(constants.SSHHost)
if !ok {
t.Fatalf("Expected the state bag to have a value for '%s', but it did not.", constants.SSHHost)
@ -96,6 +106,7 @@ func createTestStateBagStepGetIPAddress() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmPublicIPAddressName, "Unit Test: PublicIPAddressName")
stateBag.Put(constants.ArmNicName, "Unit Test: NicName")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
return stateBag

View File

@ -51,6 +51,13 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
builder.SetMarketPlaceImage(config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion)
}
if config.VirtualNetworkName != "" {
builder.SetVirtualNetwork(
config.VirtualNetworkResourceGroupName,
config.VirtualNetworkName,
config.VirtualNetworkSubnetName)
}
doc, _ := builder.ToJSON()
return createDeploymentParameters(*doc, params)
}

View File

@ -154,7 +154,8 @@
"subnetName": "packerSubnet",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}

View File

@ -152,7 +152,8 @@
"subnetName": "packerSubnet",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}

View File

@ -0,0 +1,120 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [],
"location": "[variables('location')]",
"name": "[variables('nicName')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"location": "[variables('location')]",
"name": "[parameters('vmName')]",
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"osProfile": {
"adminPassword": "[parameters('adminPassword')]",
"adminUsername": "[parameters('adminUsername')]",
"computerName": "[parameters('vmName')]",
"linuxConfiguration": {
"ssh": {
"publicKeys": [
{
"keyData": "",
"path": "[variables('sshKeyPath')]"
}
]
}
}
},
"storageProfile": {
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"image": {
"uri": "https://localhost/custom.vhd"
},
"name": "osdisk",
"osType": "Linux",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
}
}
}
},
"type": "Microsoft.Compute/virtualMachines"
}
],
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "virtualNetworkSubnetName",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "virtualNetworkName",
"virtualNetworkResourceGroup": "virtualNetworkResourceGroupName",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}

View File

@ -145,6 +145,38 @@ func TestVirtualMachineDeployment04(t *testing.T) {
}
}
func TestVirtualMachineDeployment05(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "https://localhost/custom.vhd",
"resource_group_name": "ignore",
"storage_account": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
"virtual_network_name": "virtualNetworkName",
"virtual_network_resource_group_name": "virtualNetworkResourceGroupName",
"virtual_network_subnet_name": "virtualNetworkSubnetName",
}
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
deployment, err := GetVirtualMachineDeployment(c)
if err != nil {
t.Fatal(err)
}
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
if err != nil {
t.Fatal(err)
}
}
// Ensure the link values are not set, and the concrete values are set.
func TestKeyVaultDeployment00(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())

View File

@ -18,6 +18,7 @@ const (
ArmComputeName string = "arm.ComputeName"
ArmCertificateUrl string = "arm.CertificateUrl"
ArmDeploymentName string = "arm.DeploymentName"
ArmNicName string = "arm.NicName"
ArmKeyVaultName string = "arm.KeyVaultName"
ArmLocation string = "arm.Location"
ArmOSDiskVhd string = "arm.OSDiskVhd"

View File

@ -0,0 +1,120 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "--subnet-name--",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "--virtual-network--",
"virtualNetworkResourceGroup": "--virtual-network-resource-group--",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"name": "[variables('nicName')]",
"type": "Microsoft.Network/networkInterfaces",
"location": "[variables('location')]",
"dependsOn": [],
"properties": {
"ipConfigurations": [
{
"properties": {
"privateIPAllocationMethod": "Dynamic",
"subnet": {
"id": "[variables('subnetRef')]"
}
},
"name": "ipconfig"
}
]
}
},
{
"apiVersion": "[variables('apiVersion')]",
"name": "[parameters('vmName')]",
"type": "Microsoft.Compute/virtualMachines",
"location": "[variables('location')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]",
"linuxConfiguration": {
"ssh": {
"publicKeys": [
{
"path": "[variables('sshKeyPath')]",
"keyData": "--test-ssh-authorized-key--"
}
]
}
}
},
"storageProfile": {
"osDisk": {
"osType": "Linux",
"name": "osdisk",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
},
"image": {
"uri": "http://azure/custom.vhd"
},
"caching": "ReadWrite",
"createOption": "FromImage"
}
}
}
}
]
}

View File

@ -13,8 +13,11 @@ const (
jsonPrefix = ""
jsonIndent = " "
resourceVirtualMachine = "Microsoft.Compute/virtualMachines"
resourceKeyVaults = "Microsoft.KeyVault/vaults"
resourceKeyVaults = "Microsoft.KeyVault/vaults"
resourceNetworkInterfaces = "Microsoft.Network/networkInterfaces"
resourcePublicIPAddresses = "Microsoft.Network/publicIPAddresses"
resourceVirtualMachine = "Microsoft.Compute/virtualMachines"
resourceVirtualNetworks = "Microsoft.Network/virtualNetworks"
variableSshKeyPath = "sshKeyPath"
)
@ -125,6 +128,28 @@ func (s *TemplateBuilder) SetImageUrl(imageUrl string, osType compute.OperatingS
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].Properties.PublicIPAddress = nil
return nil
}
func (s *TemplateBuilder) ToJSON() (*string, error) {
bs, err := json.MarshalIndent(s.template, jsonPrefix, jsonIndent)
@ -144,6 +169,10 @@ func (s *TemplateBuilder) getResourceByType(t string) (*Resource, error) {
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)
}
@ -156,6 +185,31 @@ 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
}
const basicTemplate = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
@ -194,8 +248,9 @@ const basicTemplate = `{
"subnetAddressPrefix": "10.0.0.0/24",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
},
"resources": [
{

View File

@ -154,7 +154,8 @@
"subnetName": "packerSubnet",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}

View File

@ -152,7 +152,8 @@
"subnetName": "packerSubnet",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}

View File

@ -0,0 +1,120 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [],
"location": "[variables('location')]",
"name": "[variables('nicName')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"location": "[variables('location')]",
"name": "[parameters('vmName')]",
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"osProfile": {
"adminPassword": "[parameters('adminPassword')]",
"adminUsername": "[parameters('adminUsername')]",
"computerName": "[parameters('vmName')]",
"linuxConfiguration": {
"ssh": {
"publicKeys": [
{
"keyData": "--test-ssh-authorized-key--",
"path": "[variables('sshKeyPath')]"
}
]
}
}
},
"storageProfile": {
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"image": {
"uri": "http://azure/custom.vhd"
},
"name": "osdisk",
"osType": "Linux",
"vhd": {
"uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]"
}
}
}
},
"type": "Microsoft.Compute/virtualMachines"
}
],
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"nicName": "packerNic",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "--subnet-name--",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "--virtual-network--",
"virtualNetworkResourceGroup": "--virtual-network-resource-group--",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}

View File

@ -168,7 +168,8 @@
"subnetName": "packerSubnet",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}

View File

@ -64,6 +64,32 @@ func TestBuildLinux01(t *testing.T) {
}
}
// Ensure that a user can specify an existing Virtual Network
func TestBuildLinux02(t *testing.T) {
testSubject, err := NewTemplateBuilder()
if err != nil {
t.Fatal(err)
}
testSubject.BuildLinux("--test-ssh-authorized-key--")
testSubject.SetImageUrl("http://azure/custom.vhd", compute.Linux)
err = testSubject.SetVirtualNetwork("--virtual-network-resource-group--", "--virtual-network--", "--subnet-name--")
if err != nil {
t.Fatal(err)
}
doc, err := testSubject.ToJSON()
if err != nil {
t.Fatal(err)
}
err = approvaltests.VerifyJSONBytes(t, []byte(*doc))
if err != nil {
t.Fatal(err)
}
}
// Ensure that a Windows template is configured as expected.
// * Include WinRM configuration.
// * Include KeyVault configuration, which is needed for WinRM.

View File

@ -83,6 +83,19 @@ builder.
configures your Tenant ID, Object ID, Key Vault Name, Key Vault Secret, and
WinRM certificate URL.
- `virtual_network_name` (string) Use a pre-existing virtual network for the VM. This option enables private
communication with the VM, no public IP address is **used** or **provisioned**. This value should only be set if
Packer is executed from a host on the same subnet / virtual network.
- `virtual_network_resource_group_name` (string) If virtual_network_name is set, this value **may** also be set. If
virtual_network_name is set, and this value is not set the builder attempts to determine the resource group
containing the virtual network. If the resource group cannot be found, or it cannot be disambiguated, this value
should be set.
- `virtual_network_subnet_name` (string) If virtual_network_name is set, this value **may** also be set. If
virtual_network_name is set, and this value is not set the builder attempts to determine the subnet to use with
the virtual network. If the subnet cannot be found, or it cannot be disambiguated, this value should be set.
- `vm_size` (string) 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`.