From f904d8e6215e3f083c383e8ace67139120cf99a3 Mon Sep 17 00:00:00 2001 From: Eugene Tolmachev Date: Wed, 20 Nov 2024 12:17:33 -0500 Subject: [PATCH 1/7] gallery image reference support for VMs --- samples/.gitignore | 1 + src/Farmer/Arm/Compute.fs | 10 +++++++- src/Farmer/Builders/Builders.Vm.fs | 23 ++++++++++++----- src/Farmer/Builders/Builders.VmScaleSet.fs | 9 ++++--- src/Farmer/Common.fs | 15 ++++++++++- src/Tests/VmScaleSet.fs | 29 ++++++++++++++++++++++ 6 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 samples/.gitignore diff --git a/samples/.gitignore b/samples/.gitignore new file mode 100644 index 000000000..22e55316b --- /dev/null +++ b/samples/.gitignore @@ -0,0 +1 @@ +farmer-deploy.json \ No newline at end of file diff --git a/src/Farmer/Arm/Compute.fs b/src/Farmer/Arm/Compute.fs index 62e3c8644..0feb3daec 100644 --- a/src/Farmer/Arm/Compute.fs +++ b/src/Farmer/Arm/Compute.fs @@ -285,7 +285,15 @@ module VirtualMachine = {| imageReference = match osDisk with - | FromImage(imageDefintion, _) -> + | FromImage(GalleryImageRef (_,SharedGalleryImageId imageId), _) -> + {| + sharedGalleryImageId = imageId + |} :> obj + | FromImage(GalleryImageRef (_,CommunityGalleryImageId imageId), _) -> + {| + communityGalleryImageId = imageId + |} :> obj + | FromImage(ImageDefinition imageDefintion, _) -> {| publisher = imageDefintion.Publisher.ArmValue offer = imageDefintion.Offer.ArmValue diff --git a/src/Farmer/Builders/Builders.Vm.fs b/src/Farmer/Builders/Builders.Vm.fs index 08a80f289..5bdb7bbc2 100644 --- a/src/Farmer/Builders/Builders.Vm.fs +++ b/src/Farmer/Builders/Builders.Vm.fs @@ -279,7 +279,8 @@ type VmConfig = { VirtualMachine = this.Name OS = match this.OsDisk with - | FromImage(image, _) -> image.OS + | FromImage(ImageDefinition image, _) -> image.OS + | FromImage(GalleryImageRef (os,_), _) -> os | _ -> raiseFarmer "Unable to determine OS for custom script when attaching an existing disk" ScriptContents = script FileUris = files @@ -292,12 +293,14 @@ type VmConfig = { // Azure AD SSH login extension match this.AadSshLogin, this.OsDisk with - | FeatureFlag.Enabled, FromImage(image, _) when + | FeatureFlag.Enabled, FromImage(ImageDefinition image, _) when image.OS = Linux && this.Identity.SystemAssigned = Disabled -> raiseFarmer "AAD SSH login requires that system assigned identity be enabled on the virtual machine." - | FeatureFlag.Enabled, FromImage(image, _) when image.OS = Windows -> + | FeatureFlag.Enabled, FromImage(ImageDefinition image, _) when image.OS = Windows -> + raiseFarmer "AAD SSH login is only supported for Linux Virtual Machines" + | FeatureFlag.Enabled, FromImage(GalleryImageRef (Windows,_), _) -> raiseFarmer "AAD SSH login is only supported for Linux Virtual Machines" // Assuming a user that attaches a disk knows to only using this extension for Linux images. | FeatureFlag.Enabled, _ -> { @@ -334,7 +337,7 @@ type VirtualMachineBuilder() = DisablePasswordAuthentication = None SshPathAndPublicKeys = None AadSshLogin = FeatureFlag.Disabled - OsDisk = FromImage(WindowsServer_2012Datacenter, { Size = 128; DiskType = Standard_LRS }) + OsDisk = FromImage(ImageDefinition WindowsServer_2012Datacenter, { Size = 128; DiskType = Standard_LRS }) AddressPrefix = "10.0.0.0/16" SubnetPrefix = "10.0.0.0/24" VNet = derived (fun config -> config.DeriveResourceName virtualNetworks "vnet") @@ -440,10 +443,18 @@ type VirtualMachineBuilder() = /// Sets the operating system of the VM. A set of samples is provided in the `CommonImages` module. [] - member _.ConfigureOs(state: VmConfig, image) = + member _.ConfigureOs(state: VmConfig, image: ImageDefinition) = + let osDisk = + match state.OsDisk with + | FromImage(_, diskInfo) -> FromImage(ImageDefinition image, diskInfo) + | AttachOsDisk _ -> raiseFarmer "Operating system from attached disk will be used" + + { state with OsDisk = osDisk } + + member _.ConfigureOs(state: VmConfig, imageRef) = let osDisk = match state.OsDisk with - | FromImage(_, diskInfo) -> FromImage(image, diskInfo) + | FromImage(_, diskInfo) -> FromImage(GalleryImageRef imageRef, diskInfo) | AttachOsDisk _ -> raiseFarmer "Operating system from attached disk will be used" { state with OsDisk = osDisk } diff --git a/src/Farmer/Builders/Builders.VmScaleSet.fs b/src/Farmer/Builders/Builders.VmScaleSet.fs index 7af7d802e..d4d8d72b0 100644 --- a/src/Farmer/Builders/Builders.VmScaleSet.fs +++ b/src/Farmer/Builders/Builders.VmScaleSet.fs @@ -120,7 +120,8 @@ type VmScaleSetConfig = { ScriptContents = vm.CustomScript.Value OS = match vm.OsDisk with - | FromImage(image, _) -> image.OS + | FromImage(ImageDefinition image, _) -> image.OS + | FromImage(GalleryImageRef (os,_), _) -> os | _ -> raiseFarmer "Unable to determine OS for custom script when attaching an existing disk" @@ -134,9 +135,11 @@ type VmScaleSetConfig = { this.Vm |> Option.bind (fun vm -> match vm.AadSshLogin, vm.OsDisk with - | FeatureFlag.Enabled, FromImage(image, _) when image.OS = Linux && vm.Identity.SystemAssigned = Disabled -> + | FeatureFlag.Enabled, FromImage(ImageDefinition image, _) when image.OS = Linux && vm.Identity.SystemAssigned = Disabled -> raiseFarmer "AAD SSH login requires that system assigned identity be enabled on the virtual machine." - | FeatureFlag.Enabled, FromImage(image, _) when image.OS = Windows -> + | FeatureFlag.Enabled, FromImage(ImageDefinition image, _) when image.OS = Windows -> + raiseFarmer "AAD SSH login is only supported for Linux Virtual Machines" + | FeatureFlag.Enabled, FromImage(GalleryImageRef (Windows,_), _) -> raiseFarmer "AAD SSH login is only supported for Linux Virtual Machines" // Assuming a user that attaches a disk knows to only using this extension for Linux images. | FeatureFlag.Enabled, _ -> diff --git a/src/Farmer/Common.fs b/src/Farmer/Common.fs index 12e93a453..0752e4773 100644 --- a/src/Farmer/Common.fs +++ b/src/Farmer/Common.fs @@ -821,6 +821,19 @@ module Vm = OS: OS } + type GalleryImageId = + | SharedGalleryImageId of string + | CommunityGalleryImageId of string + + member this.ArmValue = + match this with + | SharedGalleryImageId i -> i + | CommunityGalleryImageId i -> i + + type ImageInfo = + | ImageDefinition of ImageDefinition + | GalleryImageRef of OS * GalleryImageId + let makeVm os offer publisher sku = { Offer = Offer offer Publisher = Publisher publisher @@ -944,7 +957,7 @@ module Vm = /// VM OS disks can be created by attaching an existing disk or from a gallery image. type OsDiskCreateOption = | AttachOsDisk of OS * ManagedDiskId: LinkedResource - | FromImage of ImageDefinition * DiskInfo + | FromImage of ImageInfo * DiskInfo /// VM data disks can be created by attaching an existing disk or generating an empty disk. type DataDiskCreateOption = diff --git a/src/Tests/VmScaleSet.fs b/src/Tests/VmScaleSet.fs index 113246ec5..f07e71239 100644 --- a/src/Tests/VmScaleSet.fs +++ b/src/Tests/VmScaleSet.fs @@ -90,6 +90,35 @@ let tests = "my-scale-set" "VMSS OS profile has incorrect computer name prefix" } + test "Create a basic scale set using a gallery image" { + let deployment = arm { + add_resources [ + vmss { + name "my-scale-set" + vm_profile ( + vm { + username "azureuser" + operating_system (Linux, SharedGalleryImageId "test-image-id") + vm_size Standard_B1s + os_disk 128 StandardSSD_LRS + } + ) + } + ] + } + let jobj = deployment.Template |> Writer.toJson |> JToken.Parse + let vmss = jobj.SelectToken("resources[?(@.name=='my-scale-set')]") + Expect.isNotNull vmss "Scale set resource not generated" + let vmssProps = vmss["properties"] + Expect.isNotNull vmssProps "VMSS is missing 'properties'" + let vmProfile = vmssProps.SelectToken("virtualMachineProfile") + Expect.isNotNull vmProfile "VMSS is missing VM profile" + + Expect.equal + (vmProfile.SelectToken("storageProfile.imageReference.sharedGalleryImageId").ToString()) + "test-image-id" + "VMSS OS profile has incorrect image reference" + } test "Create a scale set linking to existing vnet" { let deployment = arm { add_resources [ From 73e961750a7b267932c7871cf96b4a5c98a28e93 Mon Sep 17 00:00:00 2001 From: Eugene Tolmachev Date: Thu, 21 Nov 2024 12:32:09 -0500 Subject: [PATCH 2/7] fantomas --- src/Farmer/Arm/Compute.fs | 12 ++++-------- src/Farmer/Builders/Builders.Vm.fs | 4 ++-- src/Farmer/Builders/Builders.VmScaleSet.fs | 8 +++++--- src/Tests/VmScaleSet.fs | 6 +++++- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Farmer/Arm/Compute.fs b/src/Farmer/Arm/Compute.fs index 0feb3daec..9e3070670 100644 --- a/src/Farmer/Arm/Compute.fs +++ b/src/Farmer/Arm/Compute.fs @@ -285,14 +285,10 @@ module VirtualMachine = {| imageReference = match osDisk with - | FromImage(GalleryImageRef (_,SharedGalleryImageId imageId), _) -> - {| - sharedGalleryImageId = imageId - |} :> obj - | FromImage(GalleryImageRef (_,CommunityGalleryImageId imageId), _) -> - {| - communityGalleryImageId = imageId - |} :> obj + | FromImage(GalleryImageRef(_, SharedGalleryImageId imageId), _) -> + {| sharedGalleryImageId = imageId |} :> obj + | FromImage(GalleryImageRef(_, CommunityGalleryImageId imageId), _) -> + {| communityGalleryImageId = imageId |} :> obj | FromImage(ImageDefinition imageDefintion, _) -> {| publisher = imageDefintion.Publisher.ArmValue diff --git a/src/Farmer/Builders/Builders.Vm.fs b/src/Farmer/Builders/Builders.Vm.fs index 5bdb7bbc2..a7460c306 100644 --- a/src/Farmer/Builders/Builders.Vm.fs +++ b/src/Farmer/Builders/Builders.Vm.fs @@ -280,7 +280,7 @@ type VmConfig = { OS = match this.OsDisk with | FromImage(ImageDefinition image, _) -> image.OS - | FromImage(GalleryImageRef (os,_), _) -> os + | FromImage(GalleryImageRef(os, _), _) -> os | _ -> raiseFarmer "Unable to determine OS for custom script when attaching an existing disk" ScriptContents = script FileUris = files @@ -300,7 +300,7 @@ type VmConfig = { "AAD SSH login requires that system assigned identity be enabled on the virtual machine." | FeatureFlag.Enabled, FromImage(ImageDefinition image, _) when image.OS = Windows -> raiseFarmer "AAD SSH login is only supported for Linux Virtual Machines" - | FeatureFlag.Enabled, FromImage(GalleryImageRef (Windows,_), _) -> + | FeatureFlag.Enabled, FromImage(GalleryImageRef(Windows, _), _) -> raiseFarmer "AAD SSH login is only supported for Linux Virtual Machines" // Assuming a user that attaches a disk knows to only using this extension for Linux images. | FeatureFlag.Enabled, _ -> { diff --git a/src/Farmer/Builders/Builders.VmScaleSet.fs b/src/Farmer/Builders/Builders.VmScaleSet.fs index d4d8d72b0..0da479aaa 100644 --- a/src/Farmer/Builders/Builders.VmScaleSet.fs +++ b/src/Farmer/Builders/Builders.VmScaleSet.fs @@ -121,7 +121,7 @@ type VmScaleSetConfig = { OS = match vm.OsDisk with | FromImage(ImageDefinition image, _) -> image.OS - | FromImage(GalleryImageRef (os,_), _) -> os + | FromImage(GalleryImageRef(os, _), _) -> os | _ -> raiseFarmer "Unable to determine OS for custom script when attaching an existing disk" @@ -135,11 +135,13 @@ type VmScaleSetConfig = { this.Vm |> Option.bind (fun vm -> match vm.AadSshLogin, vm.OsDisk with - | FeatureFlag.Enabled, FromImage(ImageDefinition image, _) when image.OS = Linux && vm.Identity.SystemAssigned = Disabled -> + | FeatureFlag.Enabled, FromImage(ImageDefinition image, _) when + image.OS = Linux && vm.Identity.SystemAssigned = Disabled + -> raiseFarmer "AAD SSH login requires that system assigned identity be enabled on the virtual machine." | FeatureFlag.Enabled, FromImage(ImageDefinition image, _) when image.OS = Windows -> raiseFarmer "AAD SSH login is only supported for Linux Virtual Machines" - | FeatureFlag.Enabled, FromImage(GalleryImageRef (Windows,_), _) -> + | FeatureFlag.Enabled, FromImage(GalleryImageRef(Windows, _), _) -> raiseFarmer "AAD SSH login is only supported for Linux Virtual Machines" // Assuming a user that attaches a disk knows to only using this extension for Linux images. | FeatureFlag.Enabled, _ -> diff --git a/src/Tests/VmScaleSet.fs b/src/Tests/VmScaleSet.fs index f07e71239..ccb430741 100644 --- a/src/Tests/VmScaleSet.fs +++ b/src/Tests/VmScaleSet.fs @@ -95,6 +95,7 @@ let tests = add_resources [ vmss { name "my-scale-set" + vm_profile ( vm { username "azureuser" @@ -106,6 +107,7 @@ let tests = } ] } + let jobj = deployment.Template |> Writer.toJson |> JToken.Parse let vmss = jobj.SelectToken("resources[?(@.name=='my-scale-set')]") Expect.isNotNull vmss "Scale set resource not generated" @@ -115,7 +117,9 @@ let tests = Expect.isNotNull vmProfile "VMSS is missing VM profile" Expect.equal - (vmProfile.SelectToken("storageProfile.imageReference.sharedGalleryImageId").ToString()) + (vmProfile + .SelectToken("storageProfile.imageReference.sharedGalleryImageId") + .ToString()) "test-image-id" "VMSS OS profile has incorrect image reference" } From ca0c4a3c18e61a67550d78d819052e4d637b98de Mon Sep 17 00:00:00 2001 From: Eugene Tolmachev Date: Thu, 21 Nov 2024 12:35:37 -0500 Subject: [PATCH 3/7] RELEASE_NOTES --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e4cc84540..0e0610aac 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,7 @@ Release Notes ============= +## vNext +- Support gallery references for VMs (`operating_system`) ## 1.9.6 - Network Interface: Support for adding Network Security Group (NSG) to Network Interface (NIC) From 7537a80eb73f83177122ad6fb81481db13de2856 Mon Sep 17 00:00:00 2001 From: Eugene Tolmachev Date: Thu, 28 Nov 2024 15:01:12 -0500 Subject: [PATCH 4/7] add security profile options --- RELEASE_NOTES.md | 1 + .../api-overview/resources/vm-scale-set.md | 4 + src/Farmer/Arm/Compute.fs | 101 +++++++++++++++++- src/Farmer/Builders/Builders.Vm.fs | 72 ++++++++++++- src/Farmer/Builders/Builders.VmScaleSet.fs | 94 +++++++++++++++- src/Farmer/Common.fs | 24 +++++ src/Tests/VirtualMachine.fs | 21 ++++ src/Tests/VmScaleSet.fs | 45 ++++++++ 8 files changed, 358 insertions(+), 4 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 0e0610aac..8833e792c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,7 @@ Release Notes ============= ## vNext - Support gallery references for VMs (`operating_system`) +- Support for VM/VMSS security profile options (#1163) ## 1.9.6 - Network Interface: Support for adding Network Security Group (NSG) to Network Interface (NIC) diff --git a/docs/content/api-overview/resources/vm-scale-set.md b/docs/content/api-overview/resources/vm-scale-set.md index 751c5e3a7..f9c3a46c6 100644 --- a/docs/content/api-overview/resources/vm-scale-set.md +++ b/docs/content/api-overview/resources/vm-scale-set.md @@ -25,6 +25,10 @@ The Virtual Machine Scale Set builder (`vmss`) creates a virtual machine scale s | vmss | scale_in_policy | Specify the policy for determining which VMs to remove when scaling in. | | vmss | scale_in_force_deletion | Indicates the VMs should be force deleted so they free the resources more quickly. | | vmss | upgrade_mode | Specify Manual, Automatic, or Rolling upgrades. Rolling upgrades require the Application Health Extension or a Health Probe to ensure newly replaced instances are healthy before replacing more of them. | +| vmss | osupgrade_automatic | Indicates whether OS upgrades should automatically be applied to scale set instances in a rolling fashion when a newer version of the OS image becomes available. Default value is false. If this is set to true for Windows based scale sets, enableAutomaticUpdates is automatically set to false and cannot be set to true. | +| vmss | osupgrade_automatic_rollback | Whether OS image rollback feature should be enabled. Enabled by default. | +| vmss | osupgrade_rolling_upgrade | Indicates whether rolling upgrade policy should be used during Auto OS Upgrade. Default value is false. Auto OS Upgrade will fallback to the default policy if no policy is defined on the VMSS. | +| vmss | osupgrade_rolling_upgrade_deferral | Indicates whether Auto OS Upgrade should undergo deferral. Deferred OS upgrades will send advanced notifications on a per-VM basis that an OS upgrade from rolling upgrades is incoming, via the IMDS tag 'Platform.PendingOSUpgrade'. The upgrade then defers until the upgrade is approved via an ApproveRollingUpgrade call. | | applicationHealthExtension | vmss | When adding the extension as a resource, this specifies the VM scale set it should be applied to. | | applicationHealthExtension | os | Operating system (Linux or Windows) to install the correct extension for that OS. | | applicationHealthExtension | protocol | Protocol (TCP, HTTP, or HTTPS) to probe, and if specifying HTTP or HTTPS, include the path. | diff --git a/src/Farmer/Arm/Compute.fs b/src/Farmer/Arm/Compute.fs index 9e3070670..2cee65ce3 100644 --- a/src/Farmer/Arm/Compute.fs +++ b/src/Farmer/Arm/Compute.fs @@ -198,6 +198,62 @@ type NetworkInterfaceConfiguration = { |} |} +type VmProxyAgentSettings = { + Enabled: bool + KeyIncarnationId: int + Mode: VmProxyAgentMode +} + +type VmSecurityProfile = { + EncryptionAtHost: bool option + EncryptionIdentity: Identity.ManagedIdentity option + ProxyAgentSettings: VmProxyAgentSettings option + SecurityType: VmSecurityType option + UefiSettings: UefiSettings option +} with + + static member Default = { + EncryptionAtHost = None + EncryptionIdentity = None + ProxyAgentSettings = None + SecurityType = None + UefiSettings = None + } + + member this.ToArmJson = {| + encryptionAtHost = this.EncryptionAtHost |> Option.toNullable + encryptionIdentity = + this.EncryptionIdentity + |> Option.map (fun x -> {| + userAssignedIdentityResourceId = + if x = ManagedIdentity.Empty then + Unchecked.defaultof<_> + else + x.ToArmJson + |}) + |> Option.defaultValue Unchecked.defaultof<_> + proxyAgentSettings = + this.ProxyAgentSettings + |> Option.map (fun x -> {| + enabled = x.Enabled + keyIncarnationId = x.KeyIncarnationId + mode = x.Mode.ArmValue + |}) + |> Option.defaultValue Unchecked.defaultof<_> + securityType = + this.SecurityType + |> Option.map _.ArmValue + |> Option.defaultValue Unchecked.defaultof<_> + uefiSettings = + this.UefiSettings + |> Option.map (fun x -> {| + secureBootEnabled = x.SecureBootEnabled + vTpmEnabled = x.VTpmEnabled + |}) + |> Option.defaultValue Unchecked.defaultof<_> + |} + + module VirtualMachine = let additionalCapabilities (dataDisks: DataDiskCreateOption list) = // If data disks use UltraSSD then enable that support @@ -399,6 +455,7 @@ type VirtualMachine = { Dependencies: ResourceId Set AvailabilityZone: string option DiagnosticsEnabled: bool option + SecurityProfile: VmSecurityProfile option StorageAccount: LinkedResource option Size: VMSize Priority: Priority option @@ -460,6 +517,10 @@ type VirtualMachine = { this.CustomData, this.PublicKeys ) + securityProfile = + this.SecurityProfile + |> Option.map _.ToArmJson + |> Option.defaultValue Unchecked.defaultof<_> storageProfile = VirtualMachine.storageProfile (this.Name, this.OsDisk, this.DataDisks, false) networkProfile = VirtualMachine.networkProfile (this.NetworkInterfaceIds, []) diagnosticsProfile = VirtualMachine.diagnosticsProfile (this.DiagnosticsEnabled, this.StorageAccount) @@ -485,11 +546,44 @@ type VirtualMachine = { zones = this.AvailabilityZone |> Option.map ResizeArray |> Option.toObj |} +type VmssAutomaticOSUpgradePolicy = { + DisableAutomaticRollback: bool option + EnableAutomaticOSUpgrade: bool option + OsRollingUpgradeDeferral: bool option + UseRollingUpgradePolicy: bool option +} with + + member this.ArmJson = {| + disableAutomaticRollback = this.DisableAutomaticRollback |> Option.toNullable + enableAutomaticOSUpgrade = this.EnableAutomaticOSUpgrade |> Option.toNullable + osRollingUpgradeDeferral = this.OsRollingUpgradeDeferral |> Option.toNullable + useRollingUpgradePolicy = this.UseRollingUpgradePolicy |> Option.toNullable + |} + + static member Default = { + DisableAutomaticRollback = None + EnableAutomaticOSUpgrade = None + OsRollingUpgradeDeferral = None + UseRollingUpgradePolicy = None + } + type ScaleSetUpgradePolicy = { Mode: VmScaleSet.UpgradeMode + AutomaticOSUpgradePolicy: VmssAutomaticOSUpgradePolicy option } with - member this.ArmJson = {| mode = this.Mode.ArmValue |} + static member Default = { + Mode = VmScaleSet.UpgradeMode.Automatic + AutomaticOSUpgradePolicy = None + } + + member this.ArmJson = {| + mode = this.Mode.ArmValue + automaticOSUpgradePolicy = + this.AutomaticOSUpgradePolicy + |> Option.map _.ArmJson + |> Option.defaultValue Unchecked.defaultof<_> + |} type ScaleSetScaleInPolicy = { // Set false when reusing disks or MAC addresses @@ -526,6 +620,7 @@ type VirtualMachineScaleSet = { Size: VMSize Capacity: int ScaleInPolicy: ScaleSetScaleInPolicy + SecurityProfile: VmSecurityProfile option UpgradePolicy: ScaleSetUpgradePolicy AutomaticRepairsPolicy: ScaleSetAutomaticRepairsPolicy option Priority: Priority option @@ -613,6 +708,10 @@ type VirtualMachineScaleSet = { this.CustomData, this.PublicKeys ) + securityProfile = + this.SecurityProfile + |> Option.map _.ToArmJson + |> Option.defaultValue Unchecked.defaultof<_> storageProfile = VirtualMachine.storageProfile (this.Name, this.OsDisk, this.DataDisks, true) networkProfile = {| diff --git a/src/Farmer/Builders/Builders.Vm.fs b/src/Farmer/Builders/Builders.Vm.fs index a7460c306..af000e167 100644 --- a/src/Farmer/Builders/Builders.Vm.fs +++ b/src/Farmer/Builders/Builders.Vm.fs @@ -45,6 +45,7 @@ type VmConfig = { VNet: ResourceRef AddressPrefix: string SubnetPrefix: string + SecurityProfile: VmSecurityProfile option Subnet: AutoGeneratedResource ApplicationSecurityGroups: LinkedResource list PublicIp: ResourceRef option @@ -189,6 +190,7 @@ type VmConfig = { AvailabilityZone = this.AvailabilityZone Location = location DiagnosticsEnabled = this.DiagnosticsEnabled + SecurityProfile = this.SecurityProfile StorageAccount = this.DiagnosticsStorageAccount |> Option.map (fun r -> r.toLinkedResource (this)) @@ -342,6 +344,7 @@ type VirtualMachineBuilder() = SubnetPrefix = "10.0.0.0/24" VNet = derived (fun config -> config.DeriveResourceName virtualNetworks "vnet") Subnet = Derived(fun config -> config.DeriveResourceName subnets "subnet") + SecurityProfile = None ApplicationSecurityGroups = [] PublicIp = automaticPublicIp IpAllocation = None @@ -378,6 +381,73 @@ type VirtualMachineBuilder() = | other -> other) } + [] + member _.Encryption(state: VmConfig, enabled) = { + state with + SecurityProfile = + state.SecurityProfile + |> Option.defaultValue VmSecurityProfile.Default + |> (fun x -> { + x with + EncryptionAtHost = Some enabled + }) + |> Some + } + + [] + member _.Encryption(state: VmConfig, identity) = { + state with + SecurityProfile = + state.SecurityProfile + |> Option.defaultValue VmSecurityProfile.Default + |> (fun x -> { + x with + EncryptionIdentity = Some identity + }) + |> Some + } + + [] + member _.ProxyAgent(state: VmConfig, (keyIncarnationId, mode)) = { + state with + SecurityProfile = + state.SecurityProfile + |> Option.defaultValue VmSecurityProfile.Default + |> (fun x -> { + x with + ProxyAgentSettings = + Some { + Enabled = true + KeyIncarnationId = keyIncarnationId + Mode = mode + } + }) + |> Some + } + + [] + member _.Uefi(state: VmConfig, settings) = { + state with + SecurityProfile = + state.SecurityProfile + |> Option.defaultValue VmSecurityProfile.Default + |> (fun x -> { x with UefiSettings = Some settings }) + |> Some + } + + [] + member _.SecurityType(state: VmConfig, securityType) = { + state with + SecurityProfile = + state.SecurityProfile + |> Option.defaultValue VmSecurityProfile.Default + |> (fun x -> { + x with + SecurityType = Some securityType + }) + |> Some + } + /// Sets the name of the VM. [] member _.Name(state: VmConfig, name) = { state with Name = name } @@ -841,8 +911,8 @@ type VirtualMachineBuilder() = [] member this.AadSshLoginEnabled(state: VmConfig, featureFlag: FeatureFlag) = { state with AadSshLogin = featureFlag } - [] /// Set the public IP for this VM + [] member _.PublicIp(state: VmConfig, ref: ResourceRef<_> Option) = { state with PublicIp = ref } member _.PublicIp(state: VmConfig, ref: ResourceRef<_>) = { state with PublicIp = Some ref } diff --git a/src/Farmer/Builders/Builders.VmScaleSet.fs b/src/Farmer/Builders/Builders.VmScaleSet.fs index 0da479aaa..6b633d865 100644 --- a/src/Farmer/Builders/Builders.VmScaleSet.fs +++ b/src/Farmer/Builders/Builders.VmScaleSet.fs @@ -218,8 +218,14 @@ type VmScaleSetConfig = { ForceDeletion = false Rules = [ ScaleInPolicyRule.Default ] } + SecurityProfile = vm.SecurityProfile Size = vm.Size - UpgradePolicy = this.UpgradePolicy |> Option.defaultValue { Mode = UpgradeMode.Automatic } + UpgradePolicy = + this.UpgradePolicy + |> Option.defaultValue { + ScaleSetUpgradePolicy.Default with + Mode = UpgradeMode.Automatic + } ZoneBalance = this.ZoneBalance Tags = this.Tags } @@ -505,7 +511,91 @@ type VirtualMachineScaleSetBuilder() = [] member _.UpgradeMode(state: VmScaleSetConfig, mode: UpgradeMode) = { state with - UpgradePolicy = { Mode = mode } |> Some + UpgradePolicy = + state.UpgradePolicy + |> Option.defaultValue ScaleSetUpgradePolicy.Default + |> (fun x -> { x with Mode = mode }) + |> Some + } + + [] + member _.OSUpgrade(state: VmScaleSetConfig, enabled) = { + state with + UpgradePolicy = + state.UpgradePolicy + |> Option.defaultValue ScaleSetUpgradePolicy.Default + |> (fun x -> { + x with + AutomaticOSUpgradePolicy = + x.AutomaticOSUpgradePolicy + |> Option.defaultValue VmssAutomaticOSUpgradePolicy.Default + |> (fun x -> { + x with + EnableAutomaticOSUpgrade = Some enabled + }) + |> Some + }) + |> Some + } + + [] + member _.OSUpgradeRollback(state: VmScaleSetConfig, enabled) = { + state with + UpgradePolicy = + state.UpgradePolicy + |> Option.defaultValue ScaleSetUpgradePolicy.Default + |> (fun x -> { + x with + AutomaticOSUpgradePolicy = + x.AutomaticOSUpgradePolicy + |> Option.defaultValue VmssAutomaticOSUpgradePolicy.Default + |> (fun x -> { + x with + DisableAutomaticRollback = Some(not enabled) + }) + |> Some + }) + |> Some + } + + [] + member _.OSUpgradeRollingUpgrade(state: VmScaleSetConfig, enabled) = { + state with + UpgradePolicy = + state.UpgradePolicy + |> Option.defaultValue ScaleSetUpgradePolicy.Default + |> (fun x -> { + x with + AutomaticOSUpgradePolicy = + x.AutomaticOSUpgradePolicy + |> Option.defaultValue VmssAutomaticOSUpgradePolicy.Default + |> (fun x -> { + x with + UseRollingUpgradePolicy = Some enabled + }) + |> Some + }) + |> Some + } + + [] + member _.OSUpgradeRollingUpgradeDeferral(state: VmScaleSetConfig, enabled) = { + state with + UpgradePolicy = + state.UpgradePolicy + |> Option.defaultValue ScaleSetUpgradePolicy.Default + |> (fun x -> { + x with + AutomaticOSUpgradePolicy = + x.AutomaticOSUpgradePolicy + |> Option.defaultValue VmssAutomaticOSUpgradePolicy.Default + |> (fun x -> { + x with + OsRollingUpgradeDeferral = Some enabled + }) + |> Some + }) + |> Some } let vmss = VirtualMachineScaleSetBuilder() \ No newline at end of file diff --git a/src/Farmer/Common.fs b/src/Farmer/Common.fs index 0752e4773..36d91c61a 100644 --- a/src/Farmer/Common.fs +++ b/src/Farmer/Common.fs @@ -217,6 +217,30 @@ module DedicatedHosts = | WindowsPerpetual -> "Windows_Server_Perpetual" module Vm = + + type UefiSettings = { + SecureBootEnabled: bool + VTpmEnabled: bool + } + + type VmProxyAgentMode = + | Audit + | Enforce + + member this.ArmValue = + match this with + | Audit -> "Audit" + | Enforce -> "Enforce" + + type VmSecurityType = + | ConfidentialVM + | TrustedLaunch + + member this.ArmValue = + match this with + | ConfidentialVM -> "ConfidentialVM" + | TrustedLaunch -> "TrustedLaunch" + type VMSize = | Basic_A0 | Basic_A1 diff --git a/src/Tests/VirtualMachine.fs b/src/Tests/VirtualMachine.fs index f96d24168..d1765b240 100644 --- a/src/Tests/VirtualMachine.fs +++ b/src/Tests/VirtualMachine.fs @@ -77,6 +77,27 @@ let tests = Expect.isNull (vmProperties.Property "priority") "Priority should not be set by default" } + test "VM with security profile and trusted launch" { + let template = + let myVm = vm { + name "myvm" + username "me" + security_type TrustedLaunch + } + + arm { add_resource myVm } + + let jobj = Newtonsoft.Json.Linq.JObject.Parse(template.Template |> Writer.toJson) + + let securityProfile = + jobj.SelectToken("resources[?(@.name=='myvm')].properties.securityProfile") + :?> Newtonsoft.Json.Linq.JObject + + Expect.equal + (string (securityProfile.Property "securityType").Value) + "TrustedLaunch" + "Expected securityProfile.securityType to be TrustedLaunch" + } test "Can create a basic virtual machine with managed boot diagnostics" { let resource = let myVm = vm { diff --git a/src/Tests/VmScaleSet.fs b/src/Tests/VmScaleSet.fs index ccb430741..d08c1b618 100644 --- a/src/Tests/VmScaleSet.fs +++ b/src/Tests/VmScaleSet.fs @@ -123,6 +123,51 @@ let tests = "test-image-id" "VMSS OS profile has incorrect image reference" } + test "Create a scale with OS upgrade options" { + let deployment = arm { + add_resources [ + vmss { + name "my-scale-set" + + vm_profile ( + vm { + username "azureuser" + operating_system (Linux, SharedGalleryImageId "test-image-id") + vm_size Standard_B1s + os_disk 128 StandardSSD_LRS + } + ) + + osupgrade_automatic true + osupgrade_rolling_upgrade true + } + ] + } + + let jobj = deployment.Template |> Writer.toJson |> JToken.Parse + let vmss = jobj.SelectToken("resources[?(@.name=='my-scale-set')]") + Expect.isNotNull vmss "Scale set resource not generated" + let vmssProps = vmss["properties"] + Expect.isNotNull vmssProps "VMSS is missing 'properties'" + + Expect.equal + (vmssProps + .SelectToken("upgradePolicy.automaticOSUpgradePolicy.enableAutomaticOSUpgrade") + .ToString()) + (string true) + "VMSS OS upgrade policy is expected to be enabled" + + Expect.equal + (vmssProps + .SelectToken("upgradePolicy.automaticOSUpgradePolicy.useRollingUpgradePolicy") + .ToString()) + (string true) + "VMSS OS rolling upgrade policy is expected to be enabled" + + Expect.isNull + (vmssProps.SelectToken("upgradePolicy.automaticOSUpgradePolicy.disableAutomaticRollback")) + "VMSS OS automatic upgrade rollback is not expected to be set" + } test "Create a scale set linking to existing vnet" { let deployment = arm { add_resources [ From 379a5bd27e138721bf3cf7732f469c3305013e20 Mon Sep 17 00:00:00 2001 From: Eugene Tolmachev Date: Thu, 28 Nov 2024 17:04:51 -0500 Subject: [PATCH 5/7] stronger shared/community gallery reference type --- .../api-overview/resources/virtual-machine.md | 7 +++- src/Farmer/Arm/Compute.fs | 14 +++++--- src/Farmer/Common.fs | 10 +++--- src/Tests/VmScaleSet.fs | 35 ++++++++++++++++--- 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/docs/content/api-overview/resources/virtual-machine.md b/docs/content/api-overview/resources/virtual-machine.md index 48237a966..ff91f048f 100644 --- a/docs/content/api-overview/resources/virtual-machine.md +++ b/docs/content/api-overview/resources/virtual-machine.md @@ -24,6 +24,11 @@ In addition, every VM you create will add a SecureString parameter to the ARM te | diagnostics_support | Turns on diagnostics support using an automatically created storage account. | | diagnostics_support_managed | Turns on diagnostics support using an Azure-managed storage account. | | diagnostics_support_external | Turns on diagnostics support using an existing storage account. | +| encryption_atHost | This property can be used by user in the request to enable or disable the Host Encryption for the virtual machine or virtual machine scale set. This will enable the encryption for all the disks including Resource/Temp disk at host itself. The default behavior is: The Encryption at host will be disabled unless this property is set to true for the resource. | +| encryption_identity | Specifies the Managed Identity used by ADE to get access token for keyvault operations. | +| proxy_agent | Specifies ProxyAgent settings while creating the virtual machine. | +| uefi | Specifies the security settings like secure boot and vTPM used while creating the virtual machine. | +| security_type | Specifies the SecurityType of the virtual machine. It has to be set to any specified value to enable UefiSettings. The default behavior is: UefiSettings will not be enabled unless this property is set. | | vm_size | Sets the size of the VM. | | priority | Sets the VM Priority. Only one `spot_instance` or `priority` setting is allowed per VM. No priority is set by default. | | spot_instance | Makes the VM a spot instance. Shorthand for `priority (Spot (, )`. Only one `spot_instance` or `priority` setting is allowed per VM. | @@ -60,7 +65,7 @@ In addition, every VM you create will add a SecureString parameter to the ARM te | private_ip_allocation | Sets the *private* IP as Dynamic or Static. The default is dynamic. | | ip_forwarding | Enable or disable IP forwarding on the primary network interface. Secondary NICs will leave it undefined. | | accelerated_networking | Enable or disable accelerated networking on all network interfaces generated for the VM. | -| add_ip_configuration | Add `ipConfig` definitions to add additional IP addresses or connect to multiple subnets. Connecting to additional subnets will generate a NIC for each subnet. | +| add_ip_configuration | Add `ipConfig` definitions to add additional IP addresses or connect to multiple subnets. Connecting to additional subnets will generate a NIC for each subnet. | | network_security_group | Sets the Network Security Group (NSG) for VM/NIC. Enables you to create and share firewall rule sets. | | link_to_network_security_group | Specify an existing Network Security Group (NSG) for VM/NIC. | | link_application_security_groups | Link this VM to one or more application security groups (no dependency generated). | diff --git a/src/Farmer/Arm/Compute.fs b/src/Farmer/Arm/Compute.fs index 2cee65ce3..051c099e4 100644 --- a/src/Farmer/Arm/Compute.fs +++ b/src/Farmer/Arm/Compute.fs @@ -341,10 +341,16 @@ module VirtualMachine = {| imageReference = match osDisk with - | FromImage(GalleryImageRef(_, SharedGalleryImageId imageId), _) -> - {| sharedGalleryImageId = imageId |} :> obj - | FromImage(GalleryImageRef(_, CommunityGalleryImageId imageId), _) -> - {| communityGalleryImageId = imageId |} :> obj + | FromImage(GalleryImageRef(_, (SharedGalleryImageId _ as imageRef)), _) -> + {| + sharedGalleryImageId = imageRef.ArmValue + |} + :> obj + | FromImage(GalleryImageRef(_, (CommunityGalleryImageId _ as imageRef)), _) -> + {| + communityGalleryImageId = imageRef.ArmValue + |} + :> obj | FromImage(ImageDefinition imageDefintion, _) -> {| publisher = imageDefintion.Publisher.ArmValue diff --git a/src/Farmer/Common.fs b/src/Farmer/Common.fs index 36d91c61a..04b4395a9 100644 --- a/src/Farmer/Common.fs +++ b/src/Farmer/Common.fs @@ -846,13 +846,15 @@ module Vm = } type GalleryImageId = - | SharedGalleryImageId of string - | CommunityGalleryImageId of string + | SharedGalleryImageId of gallery: ResourceName * image: ResourceName * version: string + | CommunityGalleryImageId of gallery: ResourceName * image: ResourceName * version: string member this.ArmValue = match this with - | SharedGalleryImageId i -> i - | CommunityGalleryImageId i -> i + | SharedGalleryImageId(ResourceName gallery, ResourceName image, version) -> + $"/SharedGalleries/{gallery}/Images/{image}/Versions/{version}" + | CommunityGalleryImageId(ResourceName gallery, ResourceName image, version) -> + $"/CommunityGalleries/{gallery}/Images/{image}/Versions/{version}" type ImageInfo = | ImageDefinition of ImageDefinition diff --git a/src/Tests/VmScaleSet.fs b/src/Tests/VmScaleSet.fs index d08c1b618..7fbcd4eb1 100644 --- a/src/Tests/VmScaleSet.fs +++ b/src/Tests/VmScaleSet.fs @@ -99,7 +99,16 @@ let tests = vm_profile ( vm { username "azureuser" - operating_system (Linux, SharedGalleryImageId "test-image-id") + + operating_system ( + Linux, + CommunityGalleryImageId( + ResourceName "test-gallery", + ResourceName "test-image", + "version" + ) + ) + vm_size Standard_B1s os_disk 128 StandardSSD_LRS } @@ -118,9 +127,9 @@ let tests = Expect.equal (vmProfile - .SelectToken("storageProfile.imageReference.sharedGalleryImageId") + .SelectToken("storageProfile.imageReference.communityGalleryImageId") .ToString()) - "test-image-id" + "/CommunityGalleries/test-gallery/Images/test-image/Versions/version" "VMSS OS profile has incorrect image reference" } test "Create a scale with OS upgrade options" { @@ -132,7 +141,16 @@ let tests = vm_profile ( vm { username "azureuser" - operating_system (Linux, SharedGalleryImageId "test-image-id") + + operating_system ( + Linux, + SharedGalleryImageId( + ResourceName "test-gallery", + ResourceName "test-image", + "version" + ) + ) + vm_size Standard_B1s os_disk 128 StandardSSD_LRS } @@ -149,6 +167,15 @@ let tests = Expect.isNotNull vmss "Scale set resource not generated" let vmssProps = vmss["properties"] Expect.isNotNull vmssProps "VMSS is missing 'properties'" + let vmProfile = vmssProps.SelectToken("virtualMachineProfile") + Expect.isNotNull vmProfile "VMSS is missing VM profile" + + Expect.equal + (vmProfile + .SelectToken("storageProfile.imageReference.sharedGalleryImageId") + .ToString()) + "/SharedGalleries/test-gallery/Images/test-image/Versions/version" + "VMSS OS profile has incorrect image reference" Expect.equal (vmssProps From d0d15e9821ca113034d7437cf826875e9c3a2cf4 Mon Sep 17 00:00:00 2001 From: Eugene Tolmachev Date: Thu, 28 Nov 2024 17:28:12 -0500 Subject: [PATCH 6/7] updated UEFI type for readbility --- src/Farmer/Arm/Compute.fs | 4 ++-- src/Farmer/Common.fs | 4 ++-- src/Tests/VirtualMachine.fs | 25 +++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/Farmer/Arm/Compute.fs b/src/Farmer/Arm/Compute.fs index 051c099e4..e85e24620 100644 --- a/src/Farmer/Arm/Compute.fs +++ b/src/Farmer/Arm/Compute.fs @@ -247,8 +247,8 @@ type VmSecurityProfile = { uefiSettings = this.UefiSettings |> Option.map (fun x -> {| - secureBootEnabled = x.SecureBootEnabled - vTpmEnabled = x.VTpmEnabled + secureBootEnabled = FeatureFlag.toBool x.SecureBoot + vTpmEnabled = FeatureFlag.toBool x.Vtpm |}) |> Option.defaultValue Unchecked.defaultof<_> |} diff --git a/src/Farmer/Common.fs b/src/Farmer/Common.fs index 04b4395a9..492a89de1 100644 --- a/src/Farmer/Common.fs +++ b/src/Farmer/Common.fs @@ -219,8 +219,8 @@ module DedicatedHosts = module Vm = type UefiSettings = { - SecureBootEnabled: bool - VTpmEnabled: bool + SecureBoot: FeatureFlag + Vtpm: FeatureFlag } type VmProxyAgentMode = diff --git a/src/Tests/VirtualMachine.fs b/src/Tests/VirtualMachine.fs index d1765b240..a421cd90d 100644 --- a/src/Tests/VirtualMachine.fs +++ b/src/Tests/VirtualMachine.fs @@ -98,6 +98,31 @@ let tests = "TrustedLaunch" "Expected securityProfile.securityType to be TrustedLaunch" } + test "VM with security profile and UEFI" { + let template = + let myVm = vm { + name "myvm" + username "me" + security_type TrustedLaunch + + uefi { + SecureBoot = Enabled + Vtpm = Disabled + } + } + + arm { add_resource myVm } + + let jobj = Newtonsoft.Json.Linq.JObject.Parse(template.Template |> Writer.toJson) + + let uefi = + jobj.SelectToken("resources[?(@.name=='myvm')].properties.securityProfile.uefiSettings") + :?> Newtonsoft.Json.Linq.JObject + + Expect.equal (string (uefi.Property "secureBootEnabled").Value) "True" "Expected secureBootEnabled" + + Expect.equal (string (uefi.Property "vTpmEnabled").Value) "False" "Expected vTpmEnabled to be False" + } test "Can create a basic virtual machine with managed boot diagnostics" { let resource = let myVm = vm { From 6d9525e84fe33cbb9f47dd7806333089f7eee331 Mon Sep 17 00:00:00 2001 From: Eugene Tolmachev Date: Thu, 5 Dec 2024 13:43:10 -0500 Subject: [PATCH 7/7] review changes --- .../api-overview/resources/virtual-machine.md | 5 +-- src/Farmer/Arm/Compute.fs | 11 ++++-- src/Farmer/Builders/Builders.Vm.fs | 34 ++++++++++++++++--- src/Farmer/Common.fs | 5 --- src/Tests/VirtualMachine.fs | 7 ++-- 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/docs/content/api-overview/resources/virtual-machine.md b/docs/content/api-overview/resources/virtual-machine.md index ff91f048f..ca9eb7d3b 100644 --- a/docs/content/api-overview/resources/virtual-machine.md +++ b/docs/content/api-overview/resources/virtual-machine.md @@ -24,10 +24,11 @@ In addition, every VM you create will add a SecureString parameter to the ARM te | diagnostics_support | Turns on diagnostics support using an automatically created storage account. | | diagnostics_support_managed | Turns on diagnostics support using an Azure-managed storage account. | | diagnostics_support_external | Turns on diagnostics support using an existing storage account. | -| encryption_atHost | This property can be used by user in the request to enable or disable the Host Encryption for the virtual machine or virtual machine scale set. This will enable the encryption for all the disks including Resource/Temp disk at host itself. The default behavior is: The Encryption at host will be disabled unless this property is set to true for the resource. | +| encryption_at_host | This property can be used by user in the request to enable or disable the Host Encryption for the virtual machine or virtual machine scale set. This will enable the encryption for all the disks including Resource/Temp disk at host itself. The default behavior is: The Encryption at host will be disabled unless this property is set to true for the resource. | | encryption_identity | Specifies the Managed Identity used by ADE to get access token for keyvault operations. | | proxy_agent | Specifies ProxyAgent settings while creating the virtual machine. | -| uefi | Specifies the security settings like secure boot and vTPM used while creating the virtual machine. | +| secure_boot | UEFI security settings for secure boot. | +| vtpm | UEFI security settings for vTPM. | | security_type | Specifies the SecurityType of the virtual machine. It has to be set to any specified value to enable UefiSettings. The default behavior is: UefiSettings will not be enabled unless this property is set. | | vm_size | Sets the size of the VM. | | priority | Sets the VM Priority. Only one `spot_instance` or `priority` setting is allowed per VM. No priority is set by default. | diff --git a/src/Farmer/Arm/Compute.fs b/src/Farmer/Arm/Compute.fs index e85e24620..5e2d13e10 100644 --- a/src/Farmer/Arm/Compute.fs +++ b/src/Farmer/Arm/Compute.fs @@ -204,6 +204,13 @@ type VmProxyAgentSettings = { Mode: VmProxyAgentMode } +type UefiSettings = { + SecureBoot: FeatureFlag option + Vtpm: FeatureFlag option +} with + + static member Default = { SecureBoot = None; Vtpm = None } + type VmSecurityProfile = { EncryptionAtHost: bool option EncryptionIdentity: Identity.ManagedIdentity option @@ -247,8 +254,8 @@ type VmSecurityProfile = { uefiSettings = this.UefiSettings |> Option.map (fun x -> {| - secureBootEnabled = FeatureFlag.toBool x.SecureBoot - vTpmEnabled = FeatureFlag.toBool x.Vtpm + secureBootEnabled = x.SecureBoot |> Option.map FeatureFlag.toBool |> Option.toNullable + vTpmEnabled = x.Vtpm |> Option.map FeatureFlag.toBool |> Option.toNullable |}) |> Option.defaultValue Unchecked.defaultof<_> |} diff --git a/src/Farmer/Builders/Builders.Vm.fs b/src/Farmer/Builders/Builders.Vm.fs index af000e167..0571ec910 100644 --- a/src/Farmer/Builders/Builders.Vm.fs +++ b/src/Farmer/Builders/Builders.Vm.fs @@ -381,7 +381,7 @@ type VirtualMachineBuilder() = | other -> other) } - [] + [] member _.Encryption(state: VmConfig, enabled) = { state with SecurityProfile = @@ -425,13 +425,39 @@ type VirtualMachineBuilder() = |> Some } - [] - member _.Uefi(state: VmConfig, settings) = { + [] + member _.SecureBoot(state: VmConfig, featureFlag) = { state with SecurityProfile = state.SecurityProfile |> Option.defaultValue VmSecurityProfile.Default - |> (fun x -> { x with UefiSettings = Some settings }) + |> (fun x -> { + x with + UefiSettings = + { + (x.UefiSettings |> Option.defaultValue UefiSettings.Default) with + SecureBoot = Some featureFlag + } + |> Some + }) + |> Some + } + + [] + member _.Vtpm(state: VmConfig, featureFlag) = { + state with + SecurityProfile = + state.SecurityProfile + |> Option.defaultValue VmSecurityProfile.Default + |> (fun x -> { + x with + UefiSettings = + { + (x.UefiSettings |> Option.defaultValue UefiSettings.Default) with + Vtpm = Some featureFlag + } + |> Some + }) |> Some } diff --git a/src/Farmer/Common.fs b/src/Farmer/Common.fs index 492a89de1..f956dbef7 100644 --- a/src/Farmer/Common.fs +++ b/src/Farmer/Common.fs @@ -218,11 +218,6 @@ module DedicatedHosts = module Vm = - type UefiSettings = { - SecureBoot: FeatureFlag - Vtpm: FeatureFlag - } - type VmProxyAgentMode = | Audit | Enforce diff --git a/src/Tests/VirtualMachine.fs b/src/Tests/VirtualMachine.fs index a421cd90d..11a2593ea 100644 --- a/src/Tests/VirtualMachine.fs +++ b/src/Tests/VirtualMachine.fs @@ -104,11 +104,8 @@ let tests = name "myvm" username "me" security_type TrustedLaunch - - uefi { - SecureBoot = Enabled - Vtpm = Disabled - } + secure_boot Enabled + vtpm Disabled } arm { add_resource myVm }