Merge pull request #3764 from boumenot/pr-azure-tags
azure: tag all resources
This commit is contained in:
commit
6563bbc854
|
@ -224,6 +224,7 @@ func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
|
||||||
stateBag.Put(constants.AuthorizedKey, b.config.sshAuthorizedKey)
|
stateBag.Put(constants.AuthorizedKey, b.config.sshAuthorizedKey)
|
||||||
stateBag.Put(constants.PrivateKey, b.config.sshPrivateKey)
|
stateBag.Put(constants.PrivateKey, b.config.sshPrivateKey)
|
||||||
|
|
||||||
|
stateBag.Put(constants.ArmTags, &b.config.AzureTags)
|
||||||
stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
|
stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
|
||||||
stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
|
stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
|
||||||
stateBag.Put(constants.ArmKeyVaultName, b.config.tmpKeyVaultName)
|
stateBag.Put(constants.ArmKeyVaultName, b.config.tmpKeyVaultName)
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
package arm
|
package arm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mitchellh/packer/builder/azure/common/constants"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/builder/azure/common/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
|
func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
|
||||||
|
@ -19,6 +20,7 @@ func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
|
||||||
constants.AuthorizedKey,
|
constants.AuthorizedKey,
|
||||||
constants.PrivateKey,
|
constants.PrivateKey,
|
||||||
|
|
||||||
|
constants.ArmTags,
|
||||||
constants.ArmComputeName,
|
constants.ArmComputeName,
|
||||||
constants.ArmDeploymentName,
|
constants.ArmDeploymentName,
|
||||||
constants.ArmLocation,
|
constants.ArmLocation,
|
||||||
|
|
|
@ -70,8 +70,9 @@ type Config struct {
|
||||||
VMSize string `mapstructure:"vm_size"`
|
VMSize string `mapstructure:"vm_size"`
|
||||||
|
|
||||||
// Deployment
|
// Deployment
|
||||||
ResourceGroupName string `mapstructure:"resource_group_name"`
|
AzureTags map[string]*string `mapstructure:"azure_tags"`
|
||||||
StorageAccount string `mapstructure:"storage_account"`
|
ResourceGroupName string `mapstructure:"resource_group_name"`
|
||||||
|
StorageAccount string `mapstructure:"storage_account"`
|
||||||
storageAccountBlobEndpoint string
|
storageAccountBlobEndpoint string
|
||||||
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
|
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
|
||||||
cloudEnvironment *azure.Environment
|
cloudEnvironment *azure.Environment
|
||||||
|
@ -222,6 +223,7 @@ func newConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(c.ctx)...)
|
||||||
|
|
||||||
assertRequiredParametersSet(&c, errs)
|
assertRequiredParametersSet(&c, errs)
|
||||||
|
assertTagProperties(&c, errs)
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
return nil, nil, errs
|
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) {
|
func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
|
||||||
/////////////////////////////////////////////
|
/////////////////////////////////////////////
|
||||||
// Authentication via OAUTH
|
// Authentication via OAUTH
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package arm
|
package arm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -32,16 +33,16 @@ func TestConfigShouldProvideReasonableDefaultValues(t *testing.T) {
|
||||||
c, _, err := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
c, _, err := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||||
|
|
||||||
if err != nil {
|
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)
|
t.Fatalf(" errors: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.UserName == "" {
|
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 == "" {
|
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 != "" {
|
if c.ObjectID != "" {
|
||||||
|
@ -283,7 +284,7 @@ func TestUserShouldProvideRequiredValues(t *testing.T) {
|
||||||
// Ensure we can successfully create a config.
|
// Ensure we can successfully create a config.
|
||||||
_, _, err := newConfig(builderValues, getPackerConfiguration())
|
_, _, err := newConfig(builderValues, getPackerConfiguration())
|
||||||
if err != nil {
|
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)
|
t.Fatalf(" -> %+v\n", builderValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,7 +295,7 @@ func TestUserShouldProvideRequiredValues(t *testing.T) {
|
||||||
|
|
||||||
_, _, err := newConfig(builderValues, getPackerConfiguration())
|
_, _, err := newConfig(builderValues, getPackerConfiguration())
|
||||||
if err == nil {
|
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)
|
t.Fatalf(" -> %+v\n", builderValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,7 +375,7 @@ func TestWinRMConfigShouldSetRoundTripDecorator(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Comm.WinRMTransportDecorator == nil {
|
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") {
|
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") {
|
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 {
|
func getArmBuilderConfiguration() map[string]string {
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
for _, v := range requiredConfigValues {
|
for _, v := range requiredConfigValues {
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
|
|
||||||
type StepCreateResourceGroup struct {
|
type StepCreateResourceGroup struct {
|
||||||
client *AzureClient
|
client *AzureClient
|
||||||
create func(resourceGroupName string, location string) error
|
create func(resourceGroupName string, location string, tags *map[string]*string) error
|
||||||
say func(message string)
|
say func(message string)
|
||||||
error func(e error)
|
error func(e error)
|
||||||
}
|
}
|
||||||
|
@ -30,9 +30,10 @@ func NewStepCreateResourceGroup(client *AzureClient, ui packer.Ui) *StepCreateRe
|
||||||
return step
|
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{
|
_, err := s.client.GroupsClient.CreateOrUpdate(resourceGroupName, resources.ResourceGroup{
|
||||||
Location: &location,
|
Location: &location,
|
||||||
|
Tags: tags,
|
||||||
})
|
})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -43,11 +44,16 @@ func (s *StepCreateResourceGroup) Run(state multistep.StateBag) multistep.StepAc
|
||||||
|
|
||||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||||
var location = state.Get(constants.ArmLocation).(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(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||||
s.say(fmt.Sprintf(" -> Location : '%s'", location))
|
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 {
|
if err == nil {
|
||||||
state.Put(constants.ArmIsResourceGroupCreated, true)
|
state.Put(constants.ArmIsResourceGroupCreated, true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
func TestStepCreateResourceGroupShouldFailIfCreateFails(t *testing.T) {
|
func TestStepCreateResourceGroupShouldFailIfCreateFails(t *testing.T) {
|
||||||
var testSubject = &StepCreateResourceGroup{
|
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) {},
|
say: func(message string) {},
|
||||||
error: func(e error) {},
|
error: func(e error) {},
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ func TestStepCreateResourceGroupShouldFailIfCreateFails(t *testing.T) {
|
||||||
|
|
||||||
func TestStepCreateResourceGroupShouldPassIfCreatePasses(t *testing.T) {
|
func TestStepCreateResourceGroupShouldPassIfCreatePasses(t *testing.T) {
|
||||||
var testSubject = &StepCreateResourceGroup{
|
var testSubject = &StepCreateResourceGroup{
|
||||||
create: func(string, string) error { return nil },
|
create: func(string, string, *map[string]*string) error { return nil },
|
||||||
say: func(message string) {},
|
say: func(message string) {},
|
||||||
error: func(e error) {},
|
error: func(e error) {},
|
||||||
}
|
}
|
||||||
|
@ -52,11 +52,13 @@ func TestStepCreateResourceGroupShouldPassIfCreatePasses(t *testing.T) {
|
||||||
func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
func TestStepCreateResourceGroupShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||||
var actualResourceGroupName string
|
var actualResourceGroupName string
|
||||||
var actualLocation string
|
var actualLocation string
|
||||||
|
var actualTags *map[string]*string
|
||||||
|
|
||||||
var testSubject = &StepCreateResourceGroup{
|
var testSubject = &StepCreateResourceGroup{
|
||||||
create: func(resourceGroupName string, location string) error {
|
create: func(resourceGroupName string, location string, tags *map[string]*string) error {
|
||||||
actualResourceGroupName = resourceGroupName
|
actualResourceGroupName = resourceGroupName
|
||||||
actualLocation = location
|
actualLocation = location
|
||||||
|
actualTags = tags
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
say: func(message string) {},
|
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)
|
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 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 {
|
if actualResourceGroupName != expectedResourceGroupName {
|
||||||
t.Fatal("Expected the step to source 'constants.ArmResourceGroupName' from the state bag, but it did not.")
|
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.")
|
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)
|
_, ok := stateBag.GetOk(constants.ArmIsResourceGroupCreated)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("Expected the step to add item to stateBag['constants.ArmIsResourceGroupCreated'], but it did not.")
|
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.ArmLocation, "Unit Test: Location")
|
||||||
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||||
|
|
||||||
|
value := "Unit Test: Tags"
|
||||||
|
tags := map[string]*string{
|
||||||
|
"tag01": &value,
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBag.Put(constants.ArmTags, &tags)
|
||||||
|
|
||||||
return stateBag
|
return stateBag
|
||||||
}
|
}
|
||||||
|
|
|
@ -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')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`
|
|
|
@ -20,7 +20,11 @@ func GetKeyVaultDeployment(config *Config) (*resources.Deployment, error) {
|
||||||
TenantId: &template.TemplateParameter{Value: config.TenantID},
|
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) {
|
func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error) {
|
||||||
|
@ -34,7 +38,7 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
|
||||||
VMName: &template.TemplateParameter{Value: config.tmpComputeName},
|
VMName: &template.TemplateParameter{Value: config.tmpComputeName},
|
||||||
}
|
}
|
||||||
|
|
||||||
builder, _ := template.NewTemplateBuilder()
|
builder, _ := template.NewTemplateBuilder(template.BasicTemplate)
|
||||||
osType := compute.Linux
|
osType := compute.Linux
|
||||||
|
|
||||||
switch config.OSType {
|
switch config.OSType {
|
||||||
|
@ -58,6 +62,7 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
|
||||||
config.VirtualNetworkSubnetName)
|
config.VirtualNetworkSubnetName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder.SetTags(&config.AzureTags)
|
||||||
doc, _ := builder.ToJSON()
|
doc, _ := builder.ToJSON()
|
||||||
return createDeploymentParameters(*doc, params)
|
return createDeploymentParameters(*doc, params)
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,11 @@
|
||||||
"type": "secrets"
|
"type": "secrets"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"tags": {
|
||||||
|
"tag01": "value01",
|
||||||
|
"tag02": "value02",
|
||||||
|
"tag03": "value03"
|
||||||
|
},
|
||||||
"type": "Microsoft.KeyVault/vaults"
|
"type": "Microsoft.KeyVault/vaults"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -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'))]"
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,19 +23,19 @@ func TestVirtualMachineDeployment00(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if deployment.Properties.ParametersLink != nil {
|
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 {
|
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 {
|
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 {
|
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.
|
// Ensure the link values are not set, and the concrete values are set.
|
||||||
func TestKeyVaultDeployment00(t *testing.T) {
|
func TestKeyVaultDeployment00(t *testing.T) {
|
||||||
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||||
|
@ -190,19 +225,19 @@ func TestKeyVaultDeployment00(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if deployment.Properties.ParametersLink != nil {
|
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 {
|
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 {
|
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 {
|
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) {
|
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)
|
deployment, err := GetKeyVaultDeployment(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -26,5 +26,6 @@ const (
|
||||||
ArmResourceGroupName string = "arm.ResourceGroupName"
|
ArmResourceGroupName string = "arm.ResourceGroupName"
|
||||||
ArmIsResourceGroupCreated string = "arm.IsResourceGroupCreated"
|
ArmIsResourceGroupCreated string = "arm.IsResourceGroupCreated"
|
||||||
ArmStorageAccountName string = "arm.StorageAccountName"
|
ArmStorageAccountName string = "arm.StorageAccountName"
|
||||||
|
ArmTags string = "arm.Tags"
|
||||||
ArmVirtualMachineCaptureParameters string = "arm.VirtualMachineCaptureParameters"
|
ArmVirtualMachineCaptureParameters string = "arm.VirtualMachineCaptureParameters"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package template
|
||||||
import (
|
import (
|
||||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
"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/network"
|
||||||
//"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
/////////////////////////////////////////////////
|
||||||
|
@ -26,25 +25,49 @@ type Parameters struct {
|
||||||
/////////////////////////////////////////////////
|
/////////////////////////////////////////////////
|
||||||
// Template > Resource
|
// Template > Resource
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
ApiVersion *string `json:"apiVersion"`
|
ApiVersion *string `json:"apiVersion"`
|
||||||
Name *string `json:"name"`
|
Name *string `json:"name"`
|
||||||
Type *string `json:"type"`
|
Type *string `json:"type"`
|
||||||
Location *string `json:"location"`
|
Location *string `json:"location,omitempty"`
|
||||||
DependsOn *[]string `json:"dependsOn,omitempty"`
|
DependsOn *[]string `json:"dependsOn,omitempty"`
|
||||||
Properties *Properties `json:"properties,omitempty"`
|
Properties *Properties `json:"properties,omitempty"`
|
||||||
|
Tags *map[string]*string `json:"tags,omitempty"`
|
||||||
|
Resources *[]Resource `json:"resources,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
/////////////////////////////////////////////////
|
||||||
// Template > Resource > Properties
|
// Template > Resource > Properties
|
||||||
type Properties struct {
|
type Properties struct {
|
||||||
AddressSpace *network.AddressSpace `json:"addressSpace,omitempty"`
|
AccessPolicies *[]AccessPolicies `json:"accessPolicies,omitempty"`
|
||||||
DiagnosticsProfile *compute.DiagnosticsProfile `json:"diagnosticsProfile,omitempty"`
|
AddressSpace *network.AddressSpace `json:"addressSpace,omitempty"`
|
||||||
DNSSettings *network.PublicIPAddressDNSSettings `json:"dnsSettings,omitempty"`
|
DiagnosticsProfile *compute.DiagnosticsProfile `json:"diagnosticsProfile,omitempty"`
|
||||||
HardwareProfile *compute.HardwareProfile `json:"hardwareProfile,omitempty"`
|
DNSSettings *network.PublicIPAddressDNSSettings `json:"dnsSettings,omitempty"`
|
||||||
IPConfigurations *[]network.IPConfiguration `json:"ipConfigurations,omitempty"`
|
EnabledForDeployment *string `json:"enabledForDeployment,omitempty"`
|
||||||
NetworkProfile *compute.NetworkProfile `json:"networkProfile,omitempty"`
|
EnabledForTemplateDeployment *string `json:"enabledForTemplateDeployment,omitempty"`
|
||||||
OsProfile *compute.OSProfile `json:"osProfile,omitempty"`
|
HardwareProfile *compute.HardwareProfile `json:"hardwareProfile,omitempty"`
|
||||||
PublicIPAllocatedMethod *network.IPAllocationMethod `json:"publicIPAllocationMethod,omitempty"`
|
IPConfigurations *[]network.IPConfiguration `json:"ipConfigurations,omitempty"`
|
||||||
StorageProfile *compute.StorageProfile `json:"storageProfile,omitempty"`
|
NetworkProfile *compute.NetworkProfile `json:"networkProfile,omitempty"`
|
||||||
Subnets *[]network.Subnet `json:"subnets,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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,10 +26,10 @@ type TemplateBuilder struct {
|
||||||
template *Template
|
template *Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTemplateBuilder() (*TemplateBuilder, error) {
|
func NewTemplateBuilder(template string) (*TemplateBuilder, error) {
|
||||||
var t Template
|
var t Template
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(basicTemplate), &t)
|
err := json.Unmarshal([]byte(template), &t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,17 @@ func (s *TemplateBuilder) SetVirtualNetwork(virtualNetworkResourceGroup, virtual
|
||||||
return nil
|
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) {
|
func (s *TemplateBuilder) ToJSON() (*string, error) {
|
||||||
bs, err := json.MarshalIndent(s.template, jsonPrefix, jsonIndent)
|
bs, err := json.MarshalIndent(s.template, jsonPrefix, jsonIndent)
|
||||||
|
|
||||||
|
@ -210,7 +221,81 @@ func (s *TemplateBuilder) deleteResourceDependency(resource *Resource, predicate
|
||||||
*resource.DependsOn = deps
|
*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",
|
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||||
"contentVersion": "1.0.0.0",
|
"contentVersion": "1.0.0.0",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
// Ensure that a Linux template is configured as expected.
|
// Ensure that a Linux template is configured as expected.
|
||||||
// * Include SSH configuration: authorized key, and key path.
|
// * Include SSH configuration: authorized key, and key path.
|
||||||
func TestBuildLinux00(t *testing.T) {
|
func TestBuildLinux00(t *testing.T) {
|
||||||
testSubject, err := NewTemplateBuilder()
|
testSubject, err := NewTemplateBuilder(BasicTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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.
|
// Ensure that a user can specify a custom VHD when building a Linux template.
|
||||||
func TestBuildLinux01(t *testing.T) {
|
func TestBuildLinux01(t *testing.T) {
|
||||||
testSubject, err := NewTemplateBuilder()
|
testSubject, err := NewTemplateBuilder(BasicTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ func TestBuildLinux01(t *testing.T) {
|
||||||
|
|
||||||
// Ensure that a user can specify an existing Virtual Network
|
// Ensure that a user can specify an existing Virtual Network
|
||||||
func TestBuildLinux02(t *testing.T) {
|
func TestBuildLinux02(t *testing.T) {
|
||||||
testSubject, err := NewTemplateBuilder()
|
testSubject, err := NewTemplateBuilder(BasicTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ func TestBuildLinux02(t *testing.T) {
|
||||||
// * Include WinRM configuration.
|
// * Include WinRM configuration.
|
||||||
// * Include KeyVault configuration, which is needed for WinRM.
|
// * Include KeyVault configuration, which is needed for WinRM.
|
||||||
func TestBuildWindows00(t *testing.T) {
|
func TestBuildWindows00(t *testing.T) {
|
||||||
testSubject, err := NewTemplateBuilder()
|
testSubject, err := NewTemplateBuilder(BasicTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,11 @@
|
||||||
"image_offer": "UbuntuServer",
|
"image_offer": "UbuntuServer",
|
||||||
"image_sku": "16.04.0-LTS",
|
"image_sku": "16.04.0-LTS",
|
||||||
|
|
||||||
|
"azure_tags": {
|
||||||
|
"dept": "engineering",
|
||||||
|
"task": "image deployment"
|
||||||
|
},
|
||||||
|
|
||||||
"location": "West US",
|
"location": "West US",
|
||||||
"vm_size": "Standard_A2"
|
"vm_size": "Standard_A2"
|
||||||
}],
|
}],
|
||||||
|
|
|
@ -57,6 +57,10 @@ builder.
|
||||||
|
|
||||||
### Optional:
|
### 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
|
- `cloud_environment_name` (string) One of `Public`, `China`, `Germany`, or
|
||||||
`USGovernment`. Defaults to `Public`. Long forms such as
|
`USGovernment`. Defaults to `Public`. Long forms such as
|
||||||
`USGovernmentCloud` and `AzureUSGovernmentCloud` are also supported.
|
`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_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.
|
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
|
- `object_id` (string) Specify an OAuth Object ID to protect WinRM certificates
|
||||||
created at runtime. This variable is required when creating images based on
|
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_publisher": "Canonical",
|
||||||
"image_offer": "UbuntuServer",
|
"image_offer": "UbuntuServer",
|
||||||
"image_sku": "14.04.4-LTS",
|
"image_sku": "14.04.4-LTS",
|
||||||
|
|
||||||
|
"azure_tags": {
|
||||||
|
"dept": "engineering"
|
||||||
|
},
|
||||||
|
|
||||||
"location": "West US",
|
"location": "West US",
|
||||||
"vm_size": "Standard_A2"
|
"vm_size": "Standard_A2"
|
||||||
|
|
Loading…
Reference in New Issue