Merge pull request #5222 from Trip09/NO-TICKET-azure-allow-build-inside-vpc

[Azure] allow build inside vpc access via public IP
This commit is contained in:
Christopher Boumenot 2017-08-08 15:02:53 -07:00 committed by GitHub
commit 216317c49d
9 changed files with 315 additions and 83 deletions

View File

@ -119,7 +119,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
endpointConnectType := PublicEndpoint
if b.isPrivateNetworkCommunication() {
if b.isPublicPrivateNetworkCommunication() && b.isPrivateNetworkCommunication() {
endpointConnectType = PublicEndpointInPrivateNetwork
} else if b.isPrivateNetworkCommunication() {
endpointConnectType = PrivateEndpoint
}
@ -245,6 +247,10 @@ func (b *Builder) writeSSHPrivateKey(ui packer.Ui, debugKeyPath string) {
}
}
func (b *Builder) isPublicPrivateNetworkCommunication() bool {
return DefaultPrivateVirtualNetworkWithPublicIp != b.config.PrivateVirtualNetworkWithPublicIp
}
func (b *Builder) isPrivateNetworkCommunication() bool {
return b.config.VirtualNetworkName != ""
}

View File

@ -34,10 +34,11 @@ import (
)
const (
DefaultCloudEnvironmentName = "Public"
DefaultImageVersion = "latest"
DefaultUserName = "packer"
DefaultVMSize = "Standard_A1"
DefaultCloudEnvironmentName = "Public"
DefaultImageVersion = "latest"
DefaultUserName = "packer"
DefaultPrivateVirtualNetworkWithPublicIp = false
DefaultVMSize = "Standard_A1"
)
var (
@ -78,19 +79,20 @@ type Config struct {
manageImageLocation string
// Deployment
AzureTags map[string]*string `mapstructure:"azure_tags"`
ResourceGroupName string `mapstructure:"resource_group_name"`
StorageAccount string `mapstructure:"storage_account"`
TempComputeName string `mapstructure:"temp_compute_name"`
TempResourceGroupName string `mapstructure:"temp_resource_group_name"`
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"`
CustomDataFile string `mapstructure:"custom_data_file"`
customData string
AzureTags map[string]*string `mapstructure:"azure_tags"`
ResourceGroupName string `mapstructure:"resource_group_name"`
StorageAccount string `mapstructure:"storage_account"`
TempComputeName string `mapstructure:"temp_compute_name"`
TempResourceGroupName string `mapstructure:"temp_resource_group_name"`
storageAccountBlobEndpoint string
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
cloudEnvironment *azure.Environment
PrivateVirtualNetworkWithPublicIp bool `mapstructure:"private_virtual_network_with_public_ip"`
VirtualNetworkName string `mapstructure:"virtual_network_name"`
VirtualNetworkSubnetName string `mapstructure:"virtual_network_subnet_name"`
VirtualNetworkResourceGroupName string `mapstructure:"virtual_network_resource_group_name"`
CustomDataFile string `mapstructure:"custom_data_file"`
customData string
// OS
OSType string `mapstructure:"os_type"`

View File

@ -16,12 +16,14 @@ type EndpointType int
const (
PublicEndpoint EndpointType = iota
PrivateEndpoint
PublicEndpointInPrivateNetwork
)
var (
EndpointCommunicationText = map[EndpointType]string{
PublicEndpoint: "PublicEndpoint",
PrivateEndpoint: "PrivateEndpoint",
PublicEndpoint: "PublicEndpoint",
PrivateEndpoint: "PrivateEndpoint",
PublicEndpointInPrivateNetwork: "PublicEndpointInPrivateNetwork",
}
)
@ -46,6 +48,8 @@ func NewStepGetIPAddress(client *AzureClient, ui packer.Ui, endpoint EndpointTyp
step.get = step.getPrivateIP
case PublicEndpoint:
step.get = step.getPublicIP
case PublicEndpointInPrivateNetwork:
step.get = step.getPublicIPInPrivateNetwork
}
return step
@ -70,6 +74,11 @@ func (s *StepGetIPAddress) getPublicIP(resourceGroupName string, ipAddressName s
return *resp.IPAddress, nil
}
func (s *StepGetIPAddress) getPublicIPInPrivateNetwork(resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
s.getPrivateIP(resourceGroupName, ipAddressName, interfaceName)
return s.getPublicIP(resourceGroupName, ipAddressName, interfaceName)
}
func (s *StepGetIPAddress) Run(state multistep.StateBag) multistep.StepAction {
s.say("Getting the VM's IP address ...")

View File

@ -12,42 +12,50 @@ import (
)
func TestStepGetIPAddressShouldFailIfGetFails(t *testing.T) {
var testSubject = &StepGetIPAddress{
get: func(string, string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
endpoint: PublicEndpoint,
say: func(message string) {},
error: func(e error) {},
}
endpoints := []EndpointType{PublicEndpoint, PublicEndpointInPrivateNetwork}
stateBag := createTestStateBagStepGetIPAddress()
for _, endpoint := range endpoints {
var testSubject = &StepGetIPAddress{
get: func(string, string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
endpoint: endpoint,
say: func(message string) {},
error: func(e error) {},
}
var result = testSubject.Run(stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
stateBag := createTestStateBagStepGetIPAddress()
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
var result = testSubject.Run(stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
}
func TestStepGetIPAddressShouldPassIfGetPasses(t *testing.T) {
var testSubject = &StepGetIPAddress{
get: func(string, string, string) (string, error) { return "", nil },
endpoint: PublicEndpoint,
say: func(message string) {},
error: func(e error) {},
}
endpoints := []EndpointType{PublicEndpoint, PublicEndpointInPrivateNetwork}
stateBag := createTestStateBagStepGetIPAddress()
for _, endpoint := range endpoints {
var testSubject = &StepGetIPAddress{
get: func(string, string, string) (string, error) { return "", nil },
endpoint: endpoint,
say: func(message string) {},
error: func(e error) {},
}
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
stateBag := createTestStateBagStepGetIPAddress()
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
}
@ -55,50 +63,53 @@ func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualIPAddressName string
var actualNicName string
endpoints := []EndpointType{PublicEndpoint, PublicEndpointInPrivateNetwork}
var testSubject = &StepGetIPAddress{
get: func(resourceGroupName string, ipAddressName string, nicName string) (string, error) {
actualResourceGroupName = resourceGroupName
actualIPAddressName = ipAddressName
actualNicName = nicName
for _, endpoint := range endpoints {
var testSubject = &StepGetIPAddress{
get: func(resourceGroupName string, ipAddressName string, nicName string) (string, error) {
actualResourceGroupName = resourceGroupName
actualIPAddressName = ipAddressName
actualNicName = nicName
return "127.0.0.1", nil
},
endpoint: PublicEndpoint,
say: func(message string) {},
error: func(e error) {},
}
return "127.0.0.1", nil
},
endpoint: endpoint,
say: func(message string) {},
error: func(e error) {},
}
stateBag := createTestStateBagStepGetIPAddress()
var result = testSubject.Run(stateBag)
stateBag := createTestStateBagStepGetIPAddress()
var result = testSubject.Run(stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
var expectedIPAddressName = stateBag.Get(constants.ArmPublicIPAddressName).(string)
var expectedNicName = stateBag.Get(constants.ArmNicName).(string)
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.")
}
if actualIPAddressName != expectedIPAddressName {
t.Fatal("Expected StepGetIPAddress to source 'constants.ArmIPAddressName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
t.Fatal("Expected StepGetIPAddress to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if actualResourceGroupName != expectedResourceGroupName {
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.")
}
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)
}
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)
}
if expectedIPAddress != "127.0.0.1" {
t.Fatalf("Expected the value of stateBag[%s] to be '127.0.0.1', but got '%s'.", constants.SSHHost, expectedIPAddress)
if expectedIPAddress != "127.0.0.1" {
t.Fatalf("Expected the value of stateBag[%s] to be '127.0.0.1', but got '%s'.", constants.SSHHost, expectedIPAddress)
}
}
}

View File

@ -76,7 +76,12 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
builder.SetCustomData(config.customData)
}
if config.VirtualNetworkName != "" {
if config.VirtualNetworkName != "" && DefaultPrivateVirtualNetworkWithPublicIp != config.PrivateVirtualNetworkWithPublicIp {
builder.SetPrivateVirtualNetworWithPublicIp(
config.VirtualNetworkResourceGroupName,
config.VirtualNetworkName,
config.VirtualNetworkSubnetName)
} else if config.VirtualNetworkName != "" {
builder.SetVirtualNetwork(
config.VirtualNetworkResourceGroupName,
config.VirtualNetworkName,

View File

@ -0,0 +1,141 @@
{
"$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('publicIPAddressApiVersion')]",
"location": "[variables('location')]",
"name": "[variables('publicIPAddressName')]",
"properties": {
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
},
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
},
"type": "Microsoft.Network/publicIPAddresses"
},
{
"apiVersion": "[variables('networkInterfacesApiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]"
],
"location": "[variables('location')]",
"name": "[variables('nicName')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
},
"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": {
"imageReference": {
"offer": "--image-offer--",
"publisher": "--image-publisher--",
"sku": "--image-sku--",
"version": "--version--"
},
"osDisk": {
"caching": "ReadWrite",
"createOption": "fromImage",
"name": "osdisk",
"osType": "Linux"
}
}
},
"type": "Microsoft.Compute/virtualMachines"
}
],
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2017-03-30",
"location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01",
"nicName": "packerNic",
"publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "--virtual_network_subnet_name--",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "--virtual_network_name--",
"virtualNetworkResourceGroup": "--virtual_network_resource_group_name--",
"virtualNetworksApiVersion": "2017-04-01",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}

View File

@ -318,6 +318,43 @@ func TestVirtualMachineDeployment09(t *testing.T) {
}
}
// Ensure the VM template is correct when building with PublicIp and connect to Private Network
func TestVirtualMachineDeployment10(t *testing.T) {
config := map[string]interface{}{
"location": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
"image_publisher": "--image-publisher--",
"image_offer": "--image-offer--",
"image_sku": "--image-sku--",
"image_version": "--version--",
"virtual_network_resource_group_name": "--virtual_network_resource_group_name--",
"virtual_network_name": "--virtual_network_name--",
"virtual_network_subnet_name": "--virtual_network_subnet_name--",
"private_virtual_network_with_public_ip": true,
"managed_image_name": "ManagedImageName",
"managed_image_resource_group_name": "ManagedImageResourceGroupName",
}
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

@ -219,6 +219,24 @@ func (s *TemplateBuilder) SetVirtualNetwork(virtualNetworkResourceGroup, virtual
return nil
}
func (s *TemplateBuilder) SetPrivateVirtualNetworWithPublicIp(virtualNetworkResourceGroup, virtualNetworkName, subnetName string) error {
s.setVariable("virtualNetworkResourceGroup", virtualNetworkResourceGroup)
s.setVariable("virtualNetworkName", virtualNetworkName)
s.setVariable("subnetName", subnetName)
s.deleteResourceByType(resourceVirtualNetworks)
resource, err := s.getResourceByType(resourceNetworkInterfaces)
if err != nil {
return err
}
s.deleteResourceDependency(resource, func(s string) bool {
return strings.Contains(s, "Microsoft.Network/virtualNetworks")
})
return nil
}
func (s *TemplateBuilder) SetTags(tags *map[string]*string) error {
if tags == nil || len(*tags) == 0 {
return nil

View File

@ -131,9 +131,12 @@ When creating a managed image the following two options are required.
- `tenant_id` (string) The account identifier with which your `client_id` and `subscription_id` are associated. If not
specified, `tenant_id` will be looked up using `subscription_id`.
- `private_virtual_network_with_public_ip` (boolean) This value allows you to set a `virtual_network_name` and obtain
a public IP. If this value is not set and `virtual_network_name` is defined Packer is only allowed to be executed
from a host on the same subnet / virtual network.
- `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.
communication with the VM, no public IP address is **used** or **provisioned** (unless you set `private_virtual_network_with_public_ip`).
- `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