incorporate reviewer feedback

This commit is contained in:
Christopher Boumenot 2018-03-08 22:39:23 -08:00
parent d2e593de37
commit 1ef491d4c8
8 changed files with 253 additions and 33 deletions

View File

@ -57,6 +57,13 @@ var (
reResourceGroupName = regexp.MustCompile(validResourceGroupNameRe) reResourceGroupName = regexp.MustCompile(validResourceGroupNameRe)
) )
type PlanInformation struct {
PlanName string `mapstructure:"plan_name"`
PlanProduct string `mapstructure:"plan_product"`
PlanPublisher string `mapstructure:"plan_publisher"`
PlanPromotionCode string `mapstructure:"plan_promotion_code"`
}
type Config struct { type Config struct {
common.PackerConfig `mapstructure:",squash"` common.PackerConfig `mapstructure:",squash"`
@ -107,6 +114,7 @@ type Config struct {
VirtualNetworkResourceGroupName string `mapstructure:"virtual_network_resource_group_name"` VirtualNetworkResourceGroupName string `mapstructure:"virtual_network_resource_group_name"`
CustomDataFile string `mapstructure:"custom_data_file"` CustomDataFile string `mapstructure:"custom_data_file"`
customData string customData string
PlanInfo PlanInformation `mapstructure:"plan_info"`
// OS // OS
OSType string `mapstructure:"os_type"` OSType string `mapstructure:"os_type"`
@ -115,12 +123,6 @@ type Config struct {
// Additional Disks // Additional Disks
AdditionalDiskSize []int32 `mapstructure:"disk_additional_size"` AdditionalDiskSize []int32 `mapstructure:"disk_additional_size"`
// Plan Info
PlanName string `mapstructure:"plan_name"`
PlanProduct string `mapstructure:"plan_product"`
PlanPublisher string `mapstructure:"plan_publisher"`
PlanPromotionCode string `mapstructure:"plan_promotion_code"`
// Runtime Values // Runtime Values
UserName string UserName string
Password string Password string
@ -655,9 +657,18 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
///////////////////////////////////////////// /////////////////////////////////////////////
// Plan Info // Plan Info
if c.PlanName != "" || c.PlanProduct != "" || c.PlanPublisher != "" || c.PlanPromotionCode != "" { if c.PlanInfo.PlanName != "" || c.PlanInfo.PlanProduct != "" || c.PlanInfo.PlanPublisher != "" || c.PlanInfo.PlanPromotionCode != "" {
if c.PlanName == "" || c.PlanProduct == "" || c.PlanPublisher == "" { if c.PlanInfo.PlanName == "" || c.PlanInfo.PlanProduct == "" || c.PlanInfo.PlanPublisher == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("if either plan_name, plan_product, plan_publisher, or plan_promotion_code are defined then plan_name, plan_product, and plan_publisher must be defined")) errs = packer.MultiErrorAppend(errs, fmt.Errorf("if either plan_name, plan_product, plan_publisher, or plan_promotion_code are defined then plan_name, plan_product, and plan_publisher must be defined"))
} else {
if c.AzureTags == nil {
c.AzureTags = make(map[string]*string)
}
c.AzureTags["PlanInfo"] = &c.PlanInfo.PlanName
c.AzureTags["PlanProduct"] = &c.PlanInfo.PlanProduct
c.AzureTags["PlanPublisher"] = &c.PlanInfo.PlanPublisher
c.AzureTags["PlanPromotionCode"] = &c.PlanInfo.PlanPromotionCode
} }
} }

View File

@ -1119,32 +1119,112 @@ func TestPlanInfoConfiguration(t *testing.T) {
"communicator": "none", "communicator": "none",
} }
config["plan_name"] = "--plan-name--" planInfo := map[string]string{
"plan_name": "--plan-name--",
}
config["plan_info"] = planInfo
_, _, err := newConfig(config, getPackerConfiguration()) _, _, err := newConfig(config, getPackerConfiguration())
if err == nil { if err == nil {
t.Fatal("expected config to reject the use of plan_name without plan_product and plan_publisher") t.Fatal("expected config to reject the use of plan_name without plan_product and plan_publisher")
} }
config["plan_product"] = "--plan-product--" planInfo["plan_product"] = "--plan-product--"
_, _, err = newConfig(config, getPackerConfiguration()) _, _, err = newConfig(config, getPackerConfiguration())
if err == nil { if err == nil {
t.Fatal("expected config to reject the use of plan_name and plan_product without plan_publisher") t.Fatal("expected config to reject the use of plan_name and plan_product without plan_publisher")
} }
config["plan_publisher"] = "--plan-publisher--" planInfo["plan_publisher"] = "--plan-publisher--"
c, _, err := newConfig(config, getPackerConfiguration()) c, _, err := newConfig(config, getPackerConfiguration())
if err != nil { if err != nil {
t.Fatalf("expected config to accept a complete plan configuration: %s", err) t.Fatalf("expected config to accept a complete plan configuration: %s", err)
} }
if c.PlanName != "--plan-name--" { if c.PlanInfo.PlanName != "--plan-name--" {
t.Fatalf("Expected PlanName to be '--plan-name--', but got %q", c.PlanName) t.Fatalf("Expected PlanName to be '--plan-name--', but got %q", c.PlanInfo.PlanName)
} }
if c.PlanProduct != "--plan-product--" { if c.PlanInfo.PlanProduct != "--plan-product--" {
t.Fatalf("Expected PlanProduct to be '--plan-product--', but got %q", c.PlanProduct) t.Fatalf("Expected PlanProduct to be '--plan-product--', but got %q", c.PlanInfo.PlanProduct)
} }
if c.PlanPublisher != "--plan-publisher--" { if c.PlanInfo.PlanPublisher != "--plan-publisher--" {
t.Fatalf("Expected PlanPublisher to be '--plan-publisher--, but got %q", c.PlanPublisher) t.Fatalf("Expected PlanPublisher to be '--plan-publisher--, but got %q", c.PlanInfo.PlanPublisher)
}
}
func TestPlanInfoPromotionCode(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",
"os_type": "linux",
"communicator": "none",
"plan_info": map[string]string{
"plan_name": "--plan-name--",
"plan_product": "--plan-product--",
"plan_publisher": "--plan-publisher--",
"plan_promotion_code": "--plan-promotion-code--",
},
}
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatalf("expected config to accept plan_info configuration, but got %s", err)
}
if c.PlanInfo.PlanName != "--plan-name--" {
t.Fatalf("Expected PlanName to be '--plan-name--', but got %q", c.PlanInfo.PlanName)
}
if c.PlanInfo.PlanProduct != "--plan-product--" {
t.Fatalf("Expected PlanProduct to be '--plan-product--', but got %q", c.PlanInfo.PlanProduct)
}
if c.PlanInfo.PlanPublisher != "--plan-publisher--" {
t.Fatalf("Expected PlanPublisher to be '--plan-publisher--, but got %q", c.PlanInfo.PlanPublisher)
}
if c.PlanInfo.PlanPromotionCode != "--plan-promotion-code--" {
t.Fatalf("Expected PlanPublisher to be '--plan-promotion-code----, but got %q", c.PlanInfo.PlanPromotionCode)
}
}
// plan_info defines 3 or 4 tags based on plan data.
// The user can define up to 15 tags. If the combination of these two
// exceeds the max tag amount, the builder should reject the configuration.
func TestPlanInfoTooManyTagsErrors(t *testing.T) {
exactMaxNumberOfTags := map[string]string{}
for i := 0; i < 15; i++ {
exactMaxNumberOfTags[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",
"os_type": "linux",
"communicator": "none",
"azure_tags": exactMaxNumberOfTags,
"plan_info": map[string]string{
"plan_name": "--plan-name--",
"plan_product": "--plan-product--",
"plan_publisher": "--plan-publisher--",
"plan_promotion_code": "--plan-promotion-code--",
},
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Fatal("expected config to reject configuration due to excess tags")
} }
} }

View File

@ -81,8 +81,8 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
builder.SetCustomData(config.customData) builder.SetCustomData(config.customData)
} }
if config.PlanName != "" { if config.PlanInfo.PlanName != "" {
builder.SetPlanInfo(config.PlanName, config.PlanProduct, config.PlanPublisher, config.PlanPromotionCode) builder.SetPlanInfo(config.PlanInfo.PlanName, config.PlanInfo.PlanProduct, config.PlanInfo.PlanPublisher, config.PlanInfo.PlanPromotionCode)
} }
if config.VirtualNetworkName != "" && DefaultPrivateVirtualNetworkWithPublicIp != config.PrivateVirtualNetworkWithPublicIp { if config.VirtualNetworkName != "" && DefaultPrivateVirtualNetworkWithPublicIp != config.PrivateVirtualNetworkWithPublicIp {

View File

@ -35,6 +35,12 @@
}, },
"publicIPAllocationMethod": "[variables('publicIPAddressType')]" "publicIPAllocationMethod": "[variables('publicIPAddressType')]"
}, },
"tags": {
"PlanInfo": "planName00",
"PlanProduct": "planProduct00",
"PlanPromotionCode": "",
"PlanPublisher": "planPublisher00"
},
"type": "Microsoft.Network/publicIPAddresses" "type": "Microsoft.Network/publicIPAddresses"
}, },
{ {
@ -56,6 +62,12 @@
} }
] ]
}, },
"tags": {
"PlanInfo": "planName00",
"PlanProduct": "planProduct00",
"PlanPromotionCode": "",
"PlanPublisher": "planPublisher00"
},
"type": "Microsoft.Network/virtualNetworks" "type": "Microsoft.Network/virtualNetworks"
}, },
{ {
@ -82,6 +94,12 @@
} }
] ]
}, },
"tags": {
"PlanInfo": "planName00",
"PlanProduct": "planProduct00",
"PlanPromotionCode": "",
"PlanPublisher": "planPublisher00"
},
"type": "Microsoft.Network/networkInterfaces" "type": "Microsoft.Network/networkInterfaces"
}, },
{ {
@ -144,6 +162,12 @@
} }
} }
}, },
"tags": {
"PlanInfo": "planName00",
"PlanProduct": "planProduct00",
"PlanPromotionCode": "",
"PlanPublisher": "planPublisher00"
},
"type": "Microsoft.Compute/virtualMachines" "type": "Microsoft.Compute/virtualMachines"
} }
], ],

View File

@ -35,6 +35,13 @@
}, },
"publicIPAllocationMethod": "[variables('publicIPAddressType')]" "publicIPAllocationMethod": "[variables('publicIPAddressType')]"
}, },
"tags": {
"PlanInfo": "planName00",
"PlanProduct": "planProduct00",
"PlanPromotionCode": "planPromotionCode00",
"PlanPublisher": "planPublisher00",
"dept": "engineering"
},
"type": "Microsoft.Network/publicIPAddresses" "type": "Microsoft.Network/publicIPAddresses"
}, },
{ {
@ -56,6 +63,13 @@
} }
] ]
}, },
"tags": {
"PlanInfo": "planName00",
"PlanProduct": "planProduct00",
"PlanPromotionCode": "planPromotionCode00",
"PlanPublisher": "planPublisher00",
"dept": "engineering"
},
"type": "Microsoft.Network/virtualNetworks" "type": "Microsoft.Network/virtualNetworks"
}, },
{ {
@ -82,6 +96,13 @@
} }
] ]
}, },
"tags": {
"PlanInfo": "planName00",
"PlanProduct": "planProduct00",
"PlanPromotionCode": "planPromotionCode00",
"PlanPublisher": "planPublisher00",
"dept": "engineering"
},
"type": "Microsoft.Network/networkInterfaces" "type": "Microsoft.Network/networkInterfaces"
}, },
{ {
@ -145,6 +166,13 @@
} }
} }
}, },
"tags": {
"PlanInfo": "planName00",
"PlanProduct": "planProduct00",
"PlanPromotionCode": "planPromotionCode00",
"PlanPublisher": "planPublisher00",
"dept": "engineering"
},
"type": "Microsoft.Compute/virtualMachines" "type": "Microsoft.Compute/virtualMachines"
} }
], ],

View File

@ -526,9 +526,11 @@ func TestKeyVaultDeployment03(t *testing.T) {
func TestPlanInfo01(t *testing.T) { func TestPlanInfo01(t *testing.T) {
planInfo := map[string]interface{}{ planInfo := map[string]interface{}{
"plan_info": map[string]string{
"plan_name": "planName00", "plan_name": "planName00",
"plan_product": "planProduct00", "plan_product": "planProduct00",
"plan_publisher": "planPublisher00", "plan_publisher": "planPublisher00",
},
} }
c, _, _ := newConfig(planInfo, getArmBuilderConfiguration(), getPackerConfiguration()) c, _, _ := newConfig(planInfo, getArmBuilderConfiguration(), getPackerConfiguration())
@ -545,10 +547,15 @@ func TestPlanInfo01(t *testing.T) {
func TestPlanInfo02(t *testing.T) { func TestPlanInfo02(t *testing.T) {
planInfo := map[string]interface{}{ planInfo := map[string]interface{}{
"azure_tags": map[string]string{
"dept": "engineering",
},
"plan_info": map[string]string{
"plan_name": "planName00", "plan_name": "planName00",
"plan_product": "planProduct00", "plan_product": "planProduct00",
"plan_publisher": "planPublisher00", "plan_publisher": "planPublisher00",
"plan_promotion_code": "planPromotionCode00", "plan_promotion_code": "planPromotionCode00",
},
} }
c, _, _ := newConfig(planInfo, getArmBuilderConfiguration(), getPackerConfiguration()) c, _, _ := newConfig(planInfo, getArmBuilderConfiguration(), getPackerConfiguration())

View File

@ -0,0 +1,51 @@
{
"variables": {
"client_id": "{{env `ARM_CLIENT_ID`}}",
"client_secret": "{{env `ARM_CLIENT_SECRET`}}",
"resource_group": "{{env `ARM_RESOURCE_GROUP`}}",
"storage_account": "{{env `ARM_STORAGE_ACCOUNT`}}",
"subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}"
},
"builders": [{
"type": "azure-arm",
"client_id": "{{user `client_id`}}",
"client_secret": "{{user `client_secret`}}",
"resource_group_name": "{{user `resource_group`}}",
"storage_account": "{{user `storage_account`}}",
"subscription_id": "{{user `subscription_id`}}",
"capture_container_name": "images",
"capture_name_prefix": "packer",
"os_type": "Linux",
"image_publisher": "Canonical",
"image_offer": "UbuntuServer",
"image_sku": "16.04-LTS",
"azure_tags": {
"dept": "engineering",
"task": "image deployment"
},
"plan_info": {
"plan_name": "rabbitmq",
"plan_product": "rabbitmq",
"plan_publisher": "bitnami"
},
"location": "West US",
"vm_size": "Standard_DS2_v2"
}],
"provisioners": [{
"execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'",
"inline": [
"apt-get update",
"apt-get upgrade -y",
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
],
"inline_shebang": "/bin/sh -x",
"type": "shell"
}]
}

View File

@ -162,16 +162,35 @@ Providing `temp_resource_group_name` or `location` in combination with `build_re
`Linux` this configures an SSH authorized key. For `Windows` this `Linux` this configures an SSH authorized key. For `Windows` this
configures a WinRM certificate. configures a WinRM certificate.
- `plan_name` (string) The plan name. This setting (`plan_product`, `plan_publisher`, and `plan_promotion_code`) are - `plan_info` (object) - Used for creating images from Marketplace images. Please refer to [Deploy an image with
only needed for Marketplace images. Please refer to [Deploy an image with Marketplace terms](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/cli-ps-findimage#deploy-an-image-with-marketplace-terms) for more details. Marketplace terms](https://aka.ms/azuremarketplaceapideployment) for more details. Not all Marketplace images
support programmatic deployment, and support is controlled by the image publisher.
- `plan_product` (string) The plan product. See `plan_name` for more information. An example plan_info object is defined below.
- `plan_publisher` (string) Specifies the produce of the image from the Marketplace. This value is the same value as ```json
Offer (`image_offer`). See `plan_name` for more information. {
"plan_info": {
"plan_name": "rabbitmq",
"plan_product": "rabbitmq",
"plan_publisher": "bitnami"
}
}
```
- `plan_promotion_code` (string) Some Marketplace images use a promotion code. See `plan_name` for more `plan_name` (string) - The plan name, required.
information. `plan_product` (string) - The plan product, required.
`plan_publisher` (string) - The plan publisher, required.
`plan_promotion_code` (string) - Some images accept a promotion code, optional.
Images created from the Marketplace with `plan_info` **must** specify `plan_info` whenever the image is deployed.
The builder automatically adds tags to the image to ensure this information is not lost. The following tags are
added.
1. PlanName
1. PlanProduct
1. PlanPublisher
1. PlanPromotionCode
- `temp_compute_name` (string) temporary name assigned to the VM. If this value is not set, a random value will be - `temp_compute_name` (string) temporary name assigned to the VM. If this value is not set, a random value will be
assigned. Knowing the resource group and VM name allows one to execute commands to update the VM during a Packer assigned. Knowing the resource group and VM name allows one to execute commands to update the VM during a Packer