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:
parent
95cffcae78
commit
871ca8c3d9
|
@ -9,6 +9,7 @@
|
|||
.idea
|
||||
test/.env
|
||||
*~
|
||||
*.received.*
|
||||
|
||||
website/.bundle
|
||||
website/vendor
|
||||
|
|
|
@ -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'))]"
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -22,6 +22,7 @@ func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
|
|||
constants.ArmComputeName,
|
||||
constants.ArmDeploymentName,
|
||||
constants.ArmLocation,
|
||||
constants.ArmNicName,
|
||||
constants.ArmResourceGroupName,
|
||||
constants.ArmStorageAccountName,
|
||||
constants.ArmVirtualMachineCaptureParameters,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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'))]"
|
||||
}
|
||||
}
|
|
@ -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'))]"
|
||||
}
|
||||
}
|
|
@ -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'))]"
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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": [
|
||||
{
|
||||
|
|
|
@ -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'))]"
|
||||
}
|
||||
}
|
|
@ -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'))]"
|
||||
}
|
||||
}
|
|
@ -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'))]"
|
||||
}
|
||||
}
|
|
@ -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'))]"
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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`.
|
||||
|
|
Loading…
Reference in New Issue