Merge pull request #3764 from boumenot/pr-azure-tags

azure: tag all resources
This commit is contained in:
Christopher Boumenot 2016-08-02 11:30:39 -07:00 committed by GitHub
commit 6563bbc854
17 changed files with 590 additions and 133 deletions

View File

@ -224,6 +224,7 @@ func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
stateBag.Put(constants.AuthorizedKey, b.config.sshAuthorizedKey)
stateBag.Put(constants.PrivateKey, b.config.sshPrivateKey)
stateBag.Put(constants.ArmTags, &b.config.AzureTags)
stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
stateBag.Put(constants.ArmKeyVaultName, b.config.tmpKeyVaultName)

View File

@ -4,8 +4,9 @@
package arm
import (
"github.com/mitchellh/packer/builder/azure/common/constants"
"testing"
"github.com/mitchellh/packer/builder/azure/common/constants"
)
func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
@ -19,6 +20,7 @@ func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
constants.AuthorizedKey,
constants.PrivateKey,
constants.ArmTags,
constants.ArmComputeName,
constants.ArmDeploymentName,
constants.ArmLocation,

View File

@ -70,8 +70,9 @@ type Config struct {
VMSize string `mapstructure:"vm_size"`
// Deployment
ResourceGroupName string `mapstructure:"resource_group_name"`
StorageAccount string `mapstructure:"storage_account"`
AzureTags map[string]*string `mapstructure:"azure_tags"`
ResourceGroupName string `mapstructure:"resource_group_name"`
StorageAccount string `mapstructure:"storage_account"`
storageAccountBlobEndpoint string
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
cloudEnvironment *azure.Environment
@ -222,6 +223,7 @@ func newConfig(raws ...interface{}) (*Config, []string, error) {
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(c.ctx)...)
assertRequiredParametersSet(&c, errs)
assertTagProperties(&c, errs)
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}
@ -349,6 +351,21 @@ func provideDefaultValues(c *Config) {
}
}
func assertTagProperties(c *Config, errs *packer.MultiError) {
if len(c.AzureTags) > 15 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("a max of 15 tags are supported, but %d were provided", len(c.AzureTags)))
}
for k, v := range c.AzureTags {
if len(k) > 512 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 512 character limit", k, len(k)))
}
if len(*v) > 256 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", v, len(*v)))
}
}
}
func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
/////////////////////////////////////////////
// Authentication via OAUTH

View File

@ -4,6 +4,7 @@
package arm
import (
"fmt"
"strings"
"testing"
"time"
@ -32,16 +33,16 @@ func TestConfigShouldProvideReasonableDefaultValues(t *testing.T) {
c, _, err := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
if err != nil {
t.Errorf("Expected configuration creation to succeed, but it failed!\n")
t.Error("Expected configuration creation to succeed, but it failed!\n")
t.Fatalf(" errors: %s\n", err)
}
if c.UserName == "" {
t.Errorf("Expected 'UserName' to be populated, but it was empty!")
t.Error("Expected 'UserName' to be populated, but it was empty!")
}
if c.VMSize == "" {
t.Errorf("Expected 'VMSize' to be populated, but it was empty!")
t.Error("Expected 'VMSize' to be populated, but it was empty!")
}
if c.ObjectID != "" {
@ -283,7 +284,7 @@ func TestUserShouldProvideRequiredValues(t *testing.T) {
// Ensure we can successfully create a config.
_, _, err := newConfig(builderValues, getPackerConfiguration())
if err != nil {
t.Errorf("Expected configuration creation to succeed, but it failed!\n")
t.Error("Expected configuration creation to succeed, but it failed!\n")
t.Fatalf(" -> %+v\n", builderValues)
}
@ -294,7 +295,7 @@ func TestUserShouldProvideRequiredValues(t *testing.T) {
_, _, err := newConfig(builderValues, getPackerConfiguration())
if err == nil {
t.Errorf("Expected configuration creation to fail, but it succeeded!\n")
t.Error("Expected configuration creation to fail, but it succeeded!\n")
t.Fatalf(" -> %+v\n", builderValues)
}
@ -374,7 +375,7 @@ func TestWinRMConfigShouldSetRoundTripDecorator(t *testing.T) {
}
if c.Comm.WinRMTransportDecorator == nil {
t.Errorf("Expected WinRMTransportDecorator to be set, but it was nil")
t.Error("Expected WinRMTransportDecorator to be set, but it was nil")
}
}
@ -425,10 +426,10 @@ func TestUseDeviceLoginIsDisabledForWindows(t *testing.T) {
}
if !strings.Contains(err.Error(), "client_id must be specified") {
t.Errorf("Expected to find error for 'client_id must be specified")
t.Error("Expected to find error for 'client_id must be specified")
}
if !strings.Contains(err.Error(), "client_secret must be specified") {
t.Errorf("Expected to find error for 'client_secret must be specified")
t.Error("Expected to find error for 'client_secret must be specified")
}
}
@ -533,6 +534,145 @@ func TestConfigShouldRejectMalformedCaptureContainerName(t *testing.T) {
}
}
func TestConfigShouldAcceptTags(t *testing.T) {
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
"azure_tags": map[string]string{
"tag01": "value01",
"tag02": "value02",
},
}
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
if len(c.AzureTags) != 2 {
t.Fatalf("expected to find 2 tags, but got %d", len(c.AzureTags))
}
if _, ok := c.AzureTags["tag01"]; !ok {
t.Error("expected to find key=\"tag01\", but did not")
}
if _, ok := c.AzureTags["tag02"]; !ok {
t.Error("expected to find key=\"tag02\", but did not")
}
value := c.AzureTags["tag01"]
if *value != "value01" {
t.Errorf("expected AzureTags[\"tag01\"] to have value \"value01\", but got %q", value)
}
value = c.AzureTags["tag02"]
if *value != "value02" {
t.Errorf("expected AzureTags[\"tag02\"] to have value \"value02\", but got %q", value)
}
}
func TestConfigShouldRejectTagsInExcessOf15AcceptTags(t *testing.T) {
tooManyTags := map[string]string{}
for i := 0; i < 16; i++ {
tooManyTags[fmt.Sprintf("tag%.2d", i)] = "ignored"
}
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
"azure_tags": tooManyTags,
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Fatal("expected config to reject based on an excessive amount of tags (> 15)")
}
}
func TestConfigShouldRejectExcessiveTagNameLength(t *testing.T) {
nameTooLong := make([]byte, 513)
for i := range nameTooLong {
nameTooLong[i] = 'a'
}
tags := map[string]string{}
tags[string(nameTooLong)] = "ignored"
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
"azure_tags": tags,
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Fatal("expected config to reject tag name based on length (> 512)")
}
}
func TestConfigShouldRejectExcessiveTagValueLength(t *testing.T) {
valueTooLong := make([]byte, 257)
for i := range valueTooLong {
valueTooLong[i] = 'a'
}
tags := map[string]string{}
tags["tag01"] = string(valueTooLong)
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
"azure_tags": tags,
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Fatal("expected config to reject tag value based on length (> 256)")
}
}
func getArmBuilderConfiguration() map[string]string {
m := make(map[string]string)
for _, v := range requiredConfigValues {

View File

@ -14,7 +14,7 @@ import (
type StepCreateResourceGroup struct {
client *AzureClient
create func(resourceGroupName string, location string) error
create func(resourceGroupName string, location string, tags *map[string]*string) error
say func(message string)
error func(e error)
}
@ -30,9 +30,10 @@ func NewStepCreateResourceGroup(client *AzureClient, ui packer.Ui) *StepCreateRe
return step
}
func (s *StepCreateResourceGroup) createResourceGroup(resourceGroupName string, location string) error {
func (s *StepCreateResourceGroup) createResourceGroup(resourceGroupName string, location string, tags *map[string]*string) error {
_, err := s.client.GroupsClient.CreateOrUpdate(resourceGroupName, resources.ResourceGroup{
Location: &location,
Tags: tags,
})
return err
@ -43,11 +44,16 @@ func (s *StepCreateResourceGroup) Run(state multistep.StateBag) multistep.StepAc
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var location = state.Get(constants.ArmLocation).(string)
var tags = state.Get(constants.ArmTags).(*map[string]*string)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> Location : '%s'", location))
s.say(fmt.Sprintf(" -> Tags :"))
for k, v := range *tags {
s.say(fmt.Sprintf(" ->> %s : %s", k, *v))
}
err := s.create(resourceGroupName, location)
err := s.create(resourceGroupName, location, tags)
if err == nil {
state.Put(constants.ArmIsResourceGroupCreated, true)
}

View File

@ -13,7 +13,7 @@ import (
func TestStepCreateResourceGroupShouldFailIfCreateFails(t *testing.T) {
var testSubject = &StepCreateResourceGroup{
create: func(string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
create: func(string, string, *map[string]*string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
say: func(message string) {},
error: func(e error) {},
}
@ -32,7 +32,7 @@ func TestStepCreateResourceGroupShouldFailIfCreateFails(t *testing.T) {
func TestStepCreateResourceGroupShouldPassIfCreatePasses(t *testing.T) {
var testSubject = &StepCreateResourceGroup{
create: func(string, string) error { return nil },
create: func(string, string, *map[string]*string) error { return nil },
say: func(message string) {},
error: func(e error) {},
}
@ -52,11 +52,13 @@ func TestStepCreateResourceGroupShouldPassIfCreatePasses(t *testing.T) {
func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualLocation string
var actualTags *map[string]*string
var testSubject = &StepCreateResourceGroup{
create: func(resourceGroupName string, location string) error {
create: func(resourceGroupName string, location string, tags *map[string]*string) error {
actualResourceGroupName = resourceGroupName
actualLocation = location
actualTags = tags
return nil
},
say: func(message string) {},
@ -70,8 +72,9 @@ func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
var expectedLocation = stateBag.Get(constants.ArmLocation).(string)
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
var expectedLocation = stateBag.Get(constants.ArmLocation).(string)
var expectedTags = stateBag.Get(constants.ArmTags).(*map[string]*string)
if actualResourceGroupName != expectedResourceGroupName {
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
@ -81,6 +84,10 @@ func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
}
if len(*expectedTags) != len(*actualTags) && *(*expectedTags)["tag01"] != *(*actualTags)["tag01"] {
t.Fatal("Expected the step to source 'constants.ArmTags' from the state bag, but it did not.")
}
_, ok := stateBag.GetOk(constants.ArmIsResourceGroupCreated)
if !ok {
t.Fatal("Expected the step to add item to stateBag['constants.ArmIsResourceGroupCreated'], but it did not.")
@ -93,5 +100,12 @@ func createTestStateBagStepCreateResourceGroup() multistep.StateBag {
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
value := "Unit Test: Tags"
tags := map[string]*string{
"tag01": &value,
}
stateBag.Put(constants.ArmTags, &tags)
return stateBag
}

View File

@ -1,78 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See the LICENSE file in builder/azure for license information.
package arm
// See https://github.com/Azure/azure-quickstart-templates for a extensive list of templates.
// Template to deploy a KeyVault.
//
// This template is still hard-coded unlike the ARM templates used for VMs for
// a couple of reasons.
//
// 1. The SDK defines no types for a Key Vault
// 2. The Key Vault template is relatively simple, and is static.
//
const KeyVault = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"keyVaultName": {
"type": "string"
},
"keyVaultSecretValue": {
"type": "securestring"
},
"objectId": {
"type": "string"
},
"tenantId": {
"type": "string"
}
},
"variables": {
"apiVersion": "2015-06-01",
"location": "[resourceGroup().location]",
"keyVaultSecretName": "packerKeyVaultSecret"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.KeyVault/vaults",
"name": "[parameters('keyVaultName')]",
"location": "[variables('location')]",
"properties": {
"enabledForDeployment": "true",
"enabledForTemplateDeployment": "true",
"tenantId": "[parameters('tenantId')]",
"accessPolicies": [
{
"tenantId": "[parameters('tenantId')]",
"objectId": "[parameters('objectId')]",
"permissions": {
"keys": [ "all" ],
"secrets": [ "all" ]
}
}
],
"sku": {
"name": "standard",
"family": "A"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "secrets",
"name": "[variables('keyVaultSecretName')]",
"dependsOn": [
"[concat('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]"
],
"properties": {
"value": "[parameters('keyVaultSecretValue')]"
}
}
]
}
]
}`

View File

@ -20,7 +20,11 @@ func GetKeyVaultDeployment(config *Config) (*resources.Deployment, error) {
TenantId: &template.TemplateParameter{Value: config.TenantID},
}
return createDeploymentParameters(KeyVault, params)
builder, _ := template.NewTemplateBuilder(template.KeyVault)
builder.SetTags(&config.AzureTags)
doc, _ := builder.ToJSON()
return createDeploymentParameters(*doc, params)
}
func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error) {
@ -34,7 +38,7 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
VMName: &template.TemplateParameter{Value: config.tmpComputeName},
}
builder, _ := template.NewTemplateBuilder()
builder, _ := template.NewTemplateBuilder(template.BasicTemplate)
osType := compute.Linux
switch config.OSType {
@ -58,6 +62,7 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
config.VirtualNetworkSubnetName)
}
builder.SetTags(&config.AzureTags)
doc, _ := builder.ToJSON()
return createDeploymentParameters(*doc, params)
}

View File

@ -56,6 +56,11 @@
"type": "secrets"
}
],
"tags": {
"tag01": "value01",
"tag02": "value02",
"tag03": "value03"
},
"type": "Microsoft.KeyVault/vaults"
}
],

View File

@ -0,0 +1,179 @@
{
"$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')]",
"location": "[variables('location')]",
"name": "[variables('publicIPAddressName')]",
"properties": {
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
},
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
},
"tags": {
"tag01": "value01",
"tag02": "value02",
"tag03": "value03"
},
"type": "Microsoft.Network/publicIPAddresses"
},
{
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"name": "[variables('virtualNetworkName')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]"
}
}
]
},
"tags": {
"tag01": "value01",
"tag02": "value02",
"tag03": "value03"
},
"type": "Microsoft.Network/virtualNetworks"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"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')]"
}
}
}
]
},
"tags": {
"tag01": "value01",
"tag02": "value02",
"tag03": "value03"
},
"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')]"
}
}
}
},
"tags": {
"tag01": "value01",
"tag02": "value02",
"tag03": "value03"
},
"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": "packerSubnet",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "packerNetwork",
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}

View File

@ -23,19 +23,19 @@ func TestVirtualMachineDeployment00(t *testing.T) {
}
if deployment.Properties.ParametersLink != nil {
t.Errorf("Expected the ParametersLink to be nil!")
t.Error("Expected the ParametersLink to be nil!")
}
if deployment.Properties.TemplateLink != nil {
t.Errorf("Expected the TemplateLink to be nil!")
t.Error("Expected the TemplateLink to be nil!")
}
if deployment.Properties.Parameters == nil {
t.Errorf("Expected the Parameters to not be nil!")
t.Error("Expected the Parameters to not be nil!")
}
if deployment.Properties.Template == nil {
t.Errorf("Expected the Template to not be nil!")
t.Error("Expected the Template to not be nil!")
}
}
@ -177,6 +177,41 @@ func TestVirtualMachineDeployment05(t *testing.T) {
}
}
// Verify that tags are properly applied to every resource
func TestVirtualMachineDeployment06(t *testing.T) {
config := map[string]interface{}{
"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",
"azure_tags": map[string]string{
"tag01": "value01",
"tag02": "value02",
"tag03": "value03",
},
}
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())
@ -190,19 +225,19 @@ func TestKeyVaultDeployment00(t *testing.T) {
}
if deployment.Properties.ParametersLink != nil {
t.Errorf("Expected the ParametersLink to be nil!")
t.Error("Expected the ParametersLink to be nil!")
}
if deployment.Properties.TemplateLink != nil {
t.Errorf("Expected the TemplateLink to be nil!")
t.Error("Expected the TemplateLink to be nil!")
}
if deployment.Properties.Parameters == nil {
t.Errorf("Expected the Parameters to not be nil!")
t.Error("Expected the Parameters to not be nil!")
}
if deployment.Properties.Template == nil {
t.Errorf("Expected the Template to not be nil!")
t.Error("Expected the Template to not be nil!")
}
}
@ -254,9 +289,17 @@ func TestKeyVaultDeployment02(t *testing.T) {
}
}
// Ensure the KeyVault template is correct.
// Ensure the KeyVault template is correct when tags are supplied.
func TestKeyVaultDeployment03(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfigurationWithWindows(), getPackerConfiguration())
tags := map[string]interface{}{
"azure_tags": map[string]string{
"tag01": "value01",
"tag02": "value02",
"tag03": "value03",
},
}
c, _, _ := newConfig(tags, getArmBuilderConfigurationWithWindows(), getPackerConfiguration())
deployment, err := GetKeyVaultDeployment(c)
if err != nil {
t.Fatal(err)

View File

@ -26,5 +26,6 @@ const (
ArmResourceGroupName string = "arm.ResourceGroupName"
ArmIsResourceGroupCreated string = "arm.IsResourceGroupCreated"
ArmStorageAccountName string = "arm.StorageAccountName"
ArmTags string = "arm.Tags"
ArmVirtualMachineCaptureParameters string = "arm.VirtualMachineCaptureParameters"
)

View File

@ -3,7 +3,6 @@ package template
import (
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/Azure/azure-sdk-for-go/arm/network"
//"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
)
/////////////////////////////////////////////////
@ -26,25 +25,49 @@ type Parameters struct {
/////////////////////////////////////////////////
// Template > Resource
type Resource struct {
ApiVersion *string `json:"apiVersion"`
Name *string `json:"name"`
Type *string `json:"type"`
Location *string `json:"location"`
DependsOn *[]string `json:"dependsOn,omitempty"`
Properties *Properties `json:"properties,omitempty"`
ApiVersion *string `json:"apiVersion"`
Name *string `json:"name"`
Type *string `json:"type"`
Location *string `json:"location,omitempty"`
DependsOn *[]string `json:"dependsOn,omitempty"`
Properties *Properties `json:"properties,omitempty"`
Tags *map[string]*string `json:"tags,omitempty"`
Resources *[]Resource `json:"resources,omitempty"`
}
/////////////////////////////////////////////////
// Template > Resource > Properties
type Properties struct {
AddressSpace *network.AddressSpace `json:"addressSpace,omitempty"`
DiagnosticsProfile *compute.DiagnosticsProfile `json:"diagnosticsProfile,omitempty"`
DNSSettings *network.PublicIPAddressDNSSettings `json:"dnsSettings,omitempty"`
HardwareProfile *compute.HardwareProfile `json:"hardwareProfile,omitempty"`
IPConfigurations *[]network.IPConfiguration `json:"ipConfigurations,omitempty"`
NetworkProfile *compute.NetworkProfile `json:"networkProfile,omitempty"`
OsProfile *compute.OSProfile `json:"osProfile,omitempty"`
PublicIPAllocatedMethod *network.IPAllocationMethod `json:"publicIPAllocationMethod,omitempty"`
StorageProfile *compute.StorageProfile `json:"storageProfile,omitempty"`
Subnets *[]network.Subnet `json:"subnets,omitempty"`
AccessPolicies *[]AccessPolicies `json:"accessPolicies,omitempty"`
AddressSpace *network.AddressSpace `json:"addressSpace,omitempty"`
DiagnosticsProfile *compute.DiagnosticsProfile `json:"diagnosticsProfile,omitempty"`
DNSSettings *network.PublicIPAddressDNSSettings `json:"dnsSettings,omitempty"`
EnabledForDeployment *string `json:"enabledForDeployment,omitempty"`
EnabledForTemplateDeployment *string `json:"enabledForTemplateDeployment,omitempty"`
HardwareProfile *compute.HardwareProfile `json:"hardwareProfile,omitempty"`
IPConfigurations *[]network.IPConfiguration `json:"ipConfigurations,omitempty"`
NetworkProfile *compute.NetworkProfile `json:"networkProfile,omitempty"`
OsProfile *compute.OSProfile `json:"osProfile,omitempty"`
PublicIPAllocatedMethod *network.IPAllocationMethod `json:"publicIPAllocationMethod,omitempty"`
Sku *Sku `json:"sku,omitempty"`
StorageProfile *compute.StorageProfile `json:"storageProfile,omitempty"`
Subnets *[]network.Subnet `json:"subnets,omitempty"`
TenantId *string `json:"tenantId,omitempty"`
Value *string `json:"value,omitempty"`
}
type AccessPolicies struct {
ObjectId *string `json:"objectId,omitempty"`
TenantId *string `json:"tenantId,omitempty"`
Permissions *Permissions `json:"permissions,omitempty"`
}
type Permissions struct {
Keys *[]string `json:"keys,omitempty"`
Secrets *[]string `json:"secrets,omitempty"`
}
type Sku struct {
Family *string `json:"family,omitempty"`
Name *string `json:"name,omitempty"`
}

View File

@ -26,10 +26,10 @@ type TemplateBuilder struct {
template *Template
}
func NewTemplateBuilder() (*TemplateBuilder, error) {
func NewTemplateBuilder(template string) (*TemplateBuilder, error) {
var t Template
err := json.Unmarshal([]byte(basicTemplate), &t)
err := json.Unmarshal([]byte(template), &t)
if err != nil {
return nil, err
}
@ -150,6 +150,17 @@ func (s *TemplateBuilder) SetVirtualNetwork(virtualNetworkResourceGroup, virtual
return nil
}
func (s *TemplateBuilder) SetTags(tags *map[string]*string) error {
if tags == nil || len(*tags) == 0 {
return nil
}
for i := range *s.template.Resources {
(*s.template.Resources)[i].Tags = tags
}
return nil
}
func (s *TemplateBuilder) ToJSON() (*string, error) {
bs, err := json.MarshalIndent(s.template, jsonPrefix, jsonIndent)
@ -210,7 +221,81 @@ func (s *TemplateBuilder) deleteResourceDependency(resource *Resource, predicate
*resource.DependsOn = deps
}
const basicTemplate = `{
// See https://github.com/Azure/azure-quickstart-templates for a extensive list of templates.
// Template to deploy a KeyVault.
//
// This template is still hard-coded unlike the ARM templates used for VMs for
// a couple of reasons.
//
// 1. The SDK defines no types for a Key Vault
// 2. The Key Vault template is relatively simple, and is static.
//
const KeyVault = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"keyVaultName": {
"type": "string"
},
"keyVaultSecretValue": {
"type": "securestring"
},
"objectId": {
"type": "string"
},
"tenantId": {
"type": "string"
}
},
"variables": {
"apiVersion": "2015-06-01",
"location": "[resourceGroup().location]",
"keyVaultSecretName": "packerKeyVaultSecret"
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.KeyVault/vaults",
"name": "[parameters('keyVaultName')]",
"location": "[variables('location')]",
"properties": {
"enabledForDeployment": "true",
"enabledForTemplateDeployment": "true",
"tenantId": "[parameters('tenantId')]",
"accessPolicies": [
{
"tenantId": "[parameters('tenantId')]",
"objectId": "[parameters('objectId')]",
"permissions": {
"keys": [ "all" ],
"secrets": [ "all" ]
}
}
],
"sku": {
"name": "standard",
"family": "A"
}
},
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "secrets",
"name": "[variables('keyVaultSecretName')]",
"dependsOn": [
"[concat('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]"
],
"properties": {
"value": "[parameters('keyVaultSecretValue')]"
}
}
]
}
]
}`
const BasicTemplate = `{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {

View File

@ -10,7 +10,7 @@ import (
// Ensure that a Linux template is configured as expected.
// * Include SSH configuration: authorized key, and key path.
func TestBuildLinux00(t *testing.T) {
testSubject, err := NewTemplateBuilder()
testSubject, err := NewTemplateBuilder(BasicTemplate)
if err != nil {
t.Fatal(err)
}
@ -38,7 +38,7 @@ func TestBuildLinux00(t *testing.T) {
// Ensure that a user can specify a custom VHD when building a Linux template.
func TestBuildLinux01(t *testing.T) {
testSubject, err := NewTemplateBuilder()
testSubject, err := NewTemplateBuilder(BasicTemplate)
if err != nil {
t.Fatal(err)
}
@ -66,7 +66,7 @@ func TestBuildLinux01(t *testing.T) {
// Ensure that a user can specify an existing Virtual Network
func TestBuildLinux02(t *testing.T) {
testSubject, err := NewTemplateBuilder()
testSubject, err := NewTemplateBuilder(BasicTemplate)
if err != nil {
t.Fatal(err)
}
@ -94,7 +94,7 @@ func TestBuildLinux02(t *testing.T) {
// * Include WinRM configuration.
// * Include KeyVault configuration, which is needed for WinRM.
func TestBuildWindows00(t *testing.T) {
testSubject, err := NewTemplateBuilder()
testSubject, err := NewTemplateBuilder(BasicTemplate)
if err != nil {
t.Fatal(err)
}

View File

@ -23,6 +23,11 @@
"image_offer": "UbuntuServer",
"image_sku": "16.04.0-LTS",
"azure_tags": {
"dept": "engineering",
"task": "image deployment"
},
"location": "West US",
"vm_size": "Standard_A2"
}],

View File

@ -57,6 +57,10 @@ builder.
### Optional:
- `azure_tags` (object of name/value strings) - the user can define up to 15 tags. Tag names cannot exceed 512
characters, and tag values cannot exceed 256 characters. Tags are applied to every resource deployed by a Packer
build, i.e. Resource Group, VM, NIC, VNET, Public IP, KeyVault, etc.
- `cloud_environment_name` (string) One of `Public`, `China`, `Germany`, or
`USGovernment`. Defaults to `Public`. Long forms such as
`USGovernmentCloud` and `AzureUSGovernmentCloud` are also supported.
@ -70,7 +74,8 @@ builder.
- `image_url` (string) Specify a custom VHD to use. If this value is set, do not set image_publisher, image_offer,
image_sku, or image_version.
- `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`.
- `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`.
- `object_id` (string) Specify an OAuth Object ID to protect WinRM certificates
created at runtime. This variable is required when creating images based on
@ -125,6 +130,10 @@ Here is a basic example for Azure.
"image_publisher": "Canonical",
"image_offer": "UbuntuServer",
"image_sku": "14.04.4-LTS",
"azure_tags": {
"dept": "engineering"
},
"location": "West US",
"vm_size": "Standard_A2"