diff --git a/builder/azure/arm/builder.go b/builder/azure/arm/builder.go index 6a42fc26..6a7680cc 100644 --- a/builder/azure/arm/builder.go +++ b/builder/azure/arm/builder.go @@ -303,7 +303,10 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) NewStepGetSourceImageName(azureClient, ui, &b.config, generatedData), NewStepCreateResourceGroup(azureClient, ui), } - if b.config.BuildKeyVaultName == "" { + + if b.config.SkipCreateBuildKeyVault { + ui.Message("Skipping build keyvault creation...") + } else if b.config.BuildKeyVaultName == "" { keyVaultDeploymentName := b.stateBag.Get(constants.ArmKeyVaultDeploymentName).(string) steps = append(steps, NewStepValidateTemplate(azureClient, ui, &b.config, keyVaultDeploymentName, GetCommunicatorSpecificKeyVaultDeployment), @@ -330,9 +333,14 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) steps = append(steps, NewStepCertificateInKeyVault(azureClient, ui, &b.config, secret, b.config.WinrmExpirationTime)) } + + if !b.config.SkipCreateBuildKeyVault { + steps = append(steps, + NewStepGetCertificate(azureClient, ui), + NewStepSetCertificate(&b.config, ui), + ) + } steps = append(steps, - NewStepGetCertificate(azureClient, ui), - NewStepSetCertificate(&b.config, ui), NewStepValidateTemplate(azureClient, ui, &b.config, deploymentName, getVirtualMachineDeploymentFunction), NewStepDeployTemplate(azureClient, ui, &b.config, deploymentName, getVirtualMachineDeploymentFunction, VirtualMachineTemplate), NewStepGetIPAddress(azureClient, ui, endpointConnectType), diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index 52f0babc..ab464a94 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -417,6 +417,8 @@ type Config struct { // standard or premium. The default value is standard. BuildKeyVaultSKU string `mapstructure:"build_key_vault_sku"` + SkipCreateBuildKeyVault bool `mapstructure:"skip_create_build_key_vault" required:"false"` + // Specify the Disk Encryption Set ID to use to encrypt the OS and data disks created with the VM during the build // Only supported when publishing to Shared Image Galleries, without a managed image // The disk encryption set ID can be found in the properties tab of a disk encryption set on the Azure Portal, and is labeled as its resource ID @@ -1244,6 +1246,10 @@ func assertRequiredParametersSet(c *Config, errs *packersdk.MultiError) { } } + if c.SkipCreateBuildKeyVault && !strings.EqualFold(c.Comm.Type, "winrm") { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("Communicator type must be winrm when skip_create_build_key_vault is set")) + } + ///////////////////////////////////////////// // Deployment xor := func(a, b bool) bool { diff --git a/builder/azure/arm/config.hcl2spec.go b/builder/azure/arm/config.hcl2spec.go index 3c2b3e60..d7ae23d7 100644 --- a/builder/azure/arm/config.hcl2spec.go +++ b/builder/azure/arm/config.hcl2spec.go @@ -69,6 +69,7 @@ type FlatConfig struct { BuildKeyVaultName *string `mapstructure:"build_key_vault_name" cty:"build_key_vault_name" hcl:"build_key_vault_name"` BuildKeyVaultSecretName *string `mapstructure:"build_key_vault_secret_name" cty:"build_key_vault_secret_name" hcl:"build_key_vault_secret_name"` BuildKeyVaultSKU *string `mapstructure:"build_key_vault_sku" cty:"build_key_vault_sku" hcl:"build_key_vault_sku"` + SkipCreateBuildKeyVault *bool `mapstructure:"skip_create_build_key_vault" cty:"skip_create_build_key_vault" hcl:"skip_create_build_key_vault"` DiskEncryptionSetId *string `mapstructure:"disk_encryption_set_id" cty:"disk_encryption_set_id" hcl:"disk_encryption_set_id"` PrivateVirtualNetworkWithPublicIp *bool `mapstructure:"private_virtual_network_with_public_ip" required:"false" cty:"private_virtual_network_with_public_ip" hcl:"private_virtual_network_with_public_ip"` VirtualNetworkName *string `mapstructure:"virtual_network_name" required:"false" cty:"virtual_network_name" hcl:"virtual_network_name"` @@ -215,6 +216,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "build_key_vault_name": &hcldec.AttrSpec{Name: "build_key_vault_name", Type: cty.String, Required: false}, "build_key_vault_secret_name": &hcldec.AttrSpec{Name: "build_key_vault_secret_name", Type: cty.String, Required: false}, "build_key_vault_sku": &hcldec.AttrSpec{Name: "build_key_vault_sku", Type: cty.String, Required: false}, + "skip_create_build_key_vault": &hcldec.AttrSpec{Name: "skip_create_build_key_vault", Type: cty.String, Required: false}, "disk_encryption_set_id": &hcldec.AttrSpec{Name: "disk_encryption_set_id", Type: cty.String, Required: false}, "private_virtual_network_with_public_ip": &hcldec.AttrSpec{Name: "private_virtual_network_with_public_ip", Type: cty.Bool, Required: false}, "virtual_network_name": &hcldec.AttrSpec{Name: "virtual_network_name", Type: cty.String, Required: false}, diff --git a/builder/azure/arm/template_factory.go b/builder/azure/arm/template_factory.go index 8f7683e8..3cf62c0a 100644 --- a/builder/azure/arm/template_factory.go +++ b/builder/azure/arm/template_factory.go @@ -147,7 +147,7 @@ func GetVirtualMachineTemplateBuilder(config *Config) (*template.TemplateBuilder } case constants.Target_Windows: osType = hashiVMSDK.OperatingSystemTypesWindows - err = builder.BuildWindows(config.Comm.Type, config.tmpKeyVaultName, config.tmpWinRMCertificateUrl) + err = builder.BuildWindows(config.Comm.Type, config.tmpKeyVaultName, config.tmpWinRMCertificateUrl, config.SkipCreateBuildKeyVault) if err != nil { return nil, err } diff --git a/builder/azure/common/template/template_builder.go b/builder/azure/common/template/template_builder.go index 946ec879..a33a2ec1 100644 --- a/builder/azure/common/template/template_builder.go +++ b/builder/azure/common/template/template_builder.go @@ -76,7 +76,7 @@ func (s *TemplateBuilder) BuildLinux(sshAuthorizedKey string, disablePasswordAut return nil } -func (s *TemplateBuilder) BuildWindows(communicatorType string, keyVaultName string, certificateUrl string) error { +func (s *TemplateBuilder) BuildWindows(communicatorType string, keyVaultName string, certificateUrl string, skipCreateKV bool) error { resource, err := s.getResourceByType(resourceVirtualMachine) if err != nil { return err @@ -102,24 +102,34 @@ func (s *TemplateBuilder) BuildWindows(communicatorType string, keyVaultName str } provisionVMAgent := true + basicWindowConfiguration := hashiVMSDK.WindowsConfiguration{ + ProvisionVMAgent: &provisionVMAgent, + } + if communicatorType == "ssh" { - profile.WindowsConfiguration = &hashiVMSDK.WindowsConfiguration{ - ProvisionVMAgent: &provisionVMAgent, - } + profile.WindowsConfiguration = &basicWindowConfiguration return nil } - protocol := hashiVMSDK.ProtocolTypesHTTPS - profile.WindowsConfiguration = &hashiVMSDK.WindowsConfiguration{ - ProvisionVMAgent: common.BoolPtr(true), - WinRM: &hashiVMSDK.WinRMConfiguration{ - Listeners: &[]hashiVMSDK.WinRMListener{ - { - Protocol: &protocol, - CertificateUrl: common.StringPtr(certificateUrl), + // when communicator type is winrm + if !skipCreateKV { + // when skip kv create is not set, add secrets and listener + protocol := hashiVMSDK.ProtocolTypesHTTPS + profile.WindowsConfiguration = &hashiVMSDK.WindowsConfiguration{ + ProvisionVMAgent: common.BoolPtr(true), + WinRM: &hashiVMSDK.WinRMConfiguration{ + Listeners: &[]hashiVMSDK.WinRMListener{ + { + Protocol: &protocol, + CertificateUrl: common.StringPtr(certificateUrl), + }, }, }, - }, + } + } else { + // when skip kv create is set, no need to add secrets and listener in template + profile.Secrets = nil + profile.WindowsConfiguration = &basicWindowConfiguration } return nil diff --git a/builder/azure/common/template/template_builder_test.TestBuildWindows04.approved.json b/builder/azure/common/template/template_builder_test.TestBuildWindows04.approved.json new file mode 100644 index 00000000..1a92c1fd --- /dev/null +++ b/builder/azure/common/template/template_builder_test.TestBuildWindows04.approved.json @@ -0,0 +1,193 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "adminPassword": { + "type": "securestring" + }, + "adminUsername": { + "type": "string" + }, + "commandToExecute": { + "type": "string" + }, + "dataDiskName": { + "type": "string" + }, + "dnsNameForPublicIP": { + "type": "string" + }, + "nicName": { + "type": "string" + }, + "nsgName": { + "type": "string" + }, + "osDiskName": { + "type": "string" + }, + "publicIPAddressName": { + "type": "string" + }, + "storageAccountBlobEndpoint": { + "type": "string" + }, + "subnetName": { + "type": "string" + }, + "virtualNetworkName": { + "type": "string" + }, + "vmName": { + "type": "string" + }, + "vmSize": { + "type": "string" + } + }, + "resources": [ + { + "apiVersion": "[variables('networkApiVersion')]", + "location": "[variables('location')]", + "name": "[parameters('publicIPAddressName')]", + "properties": { + "dnsSettings": { + "domainNameLabel": "[parameters('dnsNameForPublicIP')]" + }, + "publicIPAllocationMethod": "[variables('publicIPAddressType')]" + }, + "type": "Microsoft.Network/publicIPAddresses" + }, + { + "apiVersion": "[variables('networkApiVersion')]", + "location": "[variables('location')]", + "name": "[variables('virtualNetworkName')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('addressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[variables('subnetName')]", + "properties": { + "addressPrefix": "[variables('subnetAddressPrefix')]" + } + } + ] + }, + "type": "Microsoft.Network/virtualNetworks" + }, + { + "apiVersion": "[variables('networkApiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" + ], + "location": "[variables('location')]", + "name": "[parameters('nicName')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]" + }, + "subnet": { + "id": "[variables('subnetRef')]" + } + } + } + ] + }, + "type": "Microsoft.Network/networkInterfaces" + }, + { + "apiVersion": "[variables('computeApiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]" + ], + "location": "[variables('location')]", + "name": "[parameters('vmName')]", + "properties": { + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": false + } + }, + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]" + } + ] + }, + "osProfile": { + "adminPassword": "[parameters('adminPassword')]", + "adminUsername": "[parameters('adminUsername')]", + "computerName": "[parameters('vmName')]", + "windowsConfiguration": { + "provisionVMAgent": true + } + }, + "storageProfile": { + "imageReference": { + "offer": "WindowsServer", + "publisher": "MicrosoftWindowsServer", + "sku": "2012-R2-Datacenter", + "version": "latest" + }, + "osDisk": { + "caching": "ReadWrite", + "createOption": "FromImage", + "name": "[parameters('osDiskName')]", + "vhd": { + "uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]" + } + } + } + }, + "type": "Microsoft.Compute/virtualMachines" + }, + { + "apiVersion": "[variables('computeApiVersion')]", + "condition": "[not(empty(parameters('commandToExecute')))]", + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines/', parameters('vmName'))]" + ], + "location": "[variables('location')]", + "name": "[concat(parameters('vmName'), '/extension-customscript')]", + "properties": { + "autoUpgradeMinorVersion": true, + "publisher": "Microsoft.Compute", + "settings": { + "commandToExecute": "[parameters('commandToExecute')]" + }, + "type": "CustomScriptExtension", + "typeHandlerVersion": "1.10" + }, + "type": "Microsoft.Compute/virtualMachines/extensions" + } + ], + "variables": { + "addressPrefix": "10.0.0.0/16", + "computeApiVersion": "2023-03-01", + "location": "[resourceGroup().location]", + "networkApiVersion": "2023-04-01", + "publicIPAddressType": "Dynamic", + "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", + "subnetAddressPrefix": "10.0.0.0/24", + "subnetName": "[parameters('subnetName')]", + "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", + "virtualNetworkName": "[parameters('virtualNetworkName')]", + "virtualNetworkResourceGroup": "[resourceGroup().name]", + "vmStorageAccountContainerName": "images", + "vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]" + } +} \ No newline at end of file diff --git a/builder/azure/common/template/template_builder_test.TestBuildWindows05.approved.json b/builder/azure/common/template/template_builder_test.TestBuildWindows05.approved.json new file mode 100644 index 00000000..e5084777 --- /dev/null +++ b/builder/azure/common/template/template_builder_test.TestBuildWindows05.approved.json @@ -0,0 +1,206 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "adminPassword": { + "type": "securestring" + }, + "adminUsername": { + "type": "string" + }, + "commandToExecute": { + "type": "string" + }, + "dataDiskName": { + "type": "string" + }, + "dnsNameForPublicIP": { + "type": "string" + }, + "nicName": { + "type": "string" + }, + "nsgName": { + "type": "string" + }, + "osDiskName": { + "type": "string" + }, + "publicIPAddressName": { + "type": "string" + }, + "storageAccountBlobEndpoint": { + "type": "string" + }, + "subnetName": { + "type": "string" + }, + "virtualNetworkName": { + "type": "string" + }, + "vmName": { + "type": "string" + }, + "vmSize": { + "type": "string" + } + }, + "resources": [ + { + "apiVersion": "[variables('networkApiVersion')]", + "location": "[variables('location')]", + "name": "[parameters('publicIPAddressName')]", + "properties": { + "dnsSettings": { + "domainNameLabel": "[parameters('dnsNameForPublicIP')]" + }, + "publicIPAllocationMethod": "[variables('publicIPAddressType')]" + }, + "type": "Microsoft.Network/publicIPAddresses" + }, + { + "apiVersion": "[variables('networkApiVersion')]", + "location": "[variables('location')]", + "name": "[variables('virtualNetworkName')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('addressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[variables('subnetName')]", + "properties": { + "addressPrefix": "[variables('subnetAddressPrefix')]" + } + } + ] + }, + "type": "Microsoft.Network/virtualNetworks" + }, + { + "apiVersion": "[variables('networkApiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" + ], + "location": "[variables('location')]", + "name": "[parameters('nicName')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]" + }, + "subnet": { + "id": "[variables('subnetRef')]" + } + } + } + ] + }, + "type": "Microsoft.Network/networkInterfaces" + }, + { + "apiVersion": "[variables('computeApiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]" + ], + "location": "[variables('location')]", + "name": "[parameters('vmName')]", + "properties": { + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": false + } + }, + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]" + } + ] + }, + "osProfile": { + "adminPassword": "[parameters('adminPassword')]", + "adminUsername": "[parameters('adminUsername')]", + "computerName": "[parameters('vmName')]", + "secrets": [ + { + "sourceVault": { + "id": "[resourceId(resourceGroup().name, 'Microsoft.KeyVault/vaults', '--test-key-vault-name')]" + }, + "vaultCertificates": [ + { + "certificateStore": "My", + "certificateUrl": "--test-ssh-certificate-url--" + } + ] + } + ], + "windowsConfiguration": { + "provisionVMAgent": true + } + }, + "storageProfile": { + "imageReference": { + "offer": "WindowsServer", + "publisher": "MicrosoftWindowsServer", + "sku": "2012-R2-Datacenter", + "version": "latest" + }, + "osDisk": { + "caching": "ReadWrite", + "createOption": "FromImage", + "name": "[parameters('osDiskName')]", + "vhd": { + "uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]" + } + } + } + }, + "type": "Microsoft.Compute/virtualMachines" + }, + { + "apiVersion": "[variables('computeApiVersion')]", + "condition": "[not(empty(parameters('commandToExecute')))]", + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines/', parameters('vmName'))]" + ], + "location": "[variables('location')]", + "name": "[concat(parameters('vmName'), '/extension-customscript')]", + "properties": { + "autoUpgradeMinorVersion": true, + "publisher": "Microsoft.Compute", + "settings": { + "commandToExecute": "[parameters('commandToExecute')]" + }, + "type": "CustomScriptExtension", + "typeHandlerVersion": "1.10" + }, + "type": "Microsoft.Compute/virtualMachines/extensions" + } + ], + "variables": { + "addressPrefix": "10.0.0.0/16", + "computeApiVersion": "2023-03-01", + "location": "[resourceGroup().location]", + "networkApiVersion": "2023-04-01", + "publicIPAddressType": "Dynamic", + "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", + "subnetAddressPrefix": "10.0.0.0/24", + "subnetName": "[parameters('subnetName')]", + "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", + "virtualNetworkName": "[parameters('virtualNetworkName')]", + "virtualNetworkResourceGroup": "[resourceGroup().name]", + "vmStorageAccountContainerName": "images", + "vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]" + } +} \ No newline at end of file diff --git a/builder/azure/common/template/template_builder_test.go b/builder/azure/common/template/template_builder_test.go index 517f5131..b17aeec0 100644 --- a/builder/azure/common/template/template_builder_test.go +++ b/builder/azure/common/template/template_builder_test.go @@ -104,7 +104,7 @@ func TestBuildWindows00(t *testing.T) { t.Fatal(err) } - err = testSubject.BuildWindows("winrm", "--test-key-vault-name", "--test-winrm-certificate-url--") + err = testSubject.BuildWindows("winrm", "--test-key-vault-name", "--test-winrm-certificate-url--", false) if err != nil { t.Fatal(err) } @@ -129,7 +129,7 @@ func TestBuildWindows01(t *testing.T) { t.Fatal(err) } - err = testSubject.BuildWindows("winrm", "--test-key-vault-name", "--test-winrm-certificate-url--") + err = testSubject.BuildWindows("winrm", "--test-key-vault-name", "--test-winrm-certificate-url--", false) if err != nil { t.Fatal(err) } @@ -159,7 +159,7 @@ func TestBuildWindows02(t *testing.T) { t.Fatal(err) } - err = testSubject.BuildWindows("winrm", "--test-key-vault-name", "--test-winrm-certificate-url--") + err = testSubject.BuildWindows("winrm", "--test-key-vault-name", "--test-winrm-certificate-url--", false) if err != nil { t.Fatal(err) } @@ -185,7 +185,60 @@ func TestBuildWindows03(t *testing.T) { t.Fatal(err) } - err = testSubject.BuildWindows("ssh", "--test-key-vault-name", "--test-ssh-certificate-url--") + err = testSubject.BuildWindows("ssh", "--test-key-vault-name", "--test-ssh-certificate-url--", false) + if err != nil { + t.Fatal(err) + } + + err = testSubject.SetMarketPlaceImage("MicrosoftWindowsServer", "WindowsServer", "2012-R2-Datacenter", "latest", compute.CachingTypesReadWrite) + if err != nil { + t.Fatal(err) + } + + doc, err := testSubject.ToJSON() + if err != nil { + t.Fatal(err) + } + + approvaltests.VerifyJSONBytes(t, []byte(*doc)) +} + +// Ensure that a Windows template is configured as expected. +// - Include Winrm configuration. +func TestBuildWindows04(t *testing.T) { + testSubject, err := NewTemplateBuilder(BasicTemplate) + if err != nil { + t.Fatal(err) + } + + err = testSubject.BuildWindows("winrm", "--test-key-vault-name", "--test-ssh-certificate-url--", true) + if err != nil { + t.Fatal(err) + } + + err = testSubject.SetMarketPlaceImage("MicrosoftWindowsServer", "WindowsServer", "2012-R2-Datacenter", "latest", compute.CachingTypesReadWrite) + if err != nil { + t.Fatal(err) + } + + doc, err := testSubject.ToJSON() + if err != nil { + t.Fatal(err) + } + + approvaltests.VerifyJSONBytes(t, []byte(*doc)) +} + +// Ensure that a Windows template is configured as expected when skip KV create flag is set. +// - Include SSH configuration. +// - Expect no regression for SSH. +func TestBuildWindows05(t *testing.T) { + testSubject, err := NewTemplateBuilder(BasicTemplate) + if err != nil { + t.Fatal(err) + } + + err = testSubject.BuildWindows("ssh", "--test-key-vault-name", "--test-ssh-certificate-url--", true) if err != nil { t.Fatal(err) } @@ -210,7 +263,7 @@ func TestBuildEncryptedWindows(t *testing.T) { t.Fatal(err) } - err = testSubject.BuildWindows("winrm", "--test-key-vault-name", "--test-winrm-certificate-url--") + err = testSubject.BuildWindows("winrm", "--test-key-vault-name", "--test-winrm-certificate-url--", false) if err != nil { t.Fatal(err) }