From 78bf429b494b5f543ea6afea093f2622de4f9d3d Mon Sep 17 00:00:00 2001 From: david becher Date: Tue, 11 Oct 2022 10:32:23 -0700 Subject: [PATCH] Squashed commit of the following: commit c230525158aea24ff5c6e85148e5eef7f87ab605 Author: Dave Curylo Date: Wed Sep 28 22:29:43 2022 -0400 Release 1.7.10 commit a8175827a8de3e0e9904d093017b804a6d248a01 Merge: 5733c5614 fbf823160 Author: Dave Curylo Date: Wed Sep 28 18:34:03 2022 -0400 Merge pull request #979 from ahmed-ebaid/link_to_identity commit fbf823160ab78bea28f02244d2524ef5df084069 Author: Ahmed Ebaid Date: Wed Sep 28 17:20:50 2022 -0500 Running fantomas on UserAssignedIdentity commit 436d6d3bb2250290a16fc0a7f838ab50e68550b7 Author: Ahmed Ebaid Date: Wed Sep 28 15:28:31 2022 -0500 Refactor code to leverage LinkedUserAssignedIdentity commit 1ed920e724d86e7a14e8fe839e73135b304caf6b Author: Dave Curylo Date: Wed Sep 28 15:24:09 2022 -0400 Reformatting for fantomas commit 667d0e865433b5c331cb729f394a41c8dfbb3118 Author: Dave Curylo Date: Wed Sep 28 15:06:48 2022 -0400 The LinkedUserAssignedIdentity is always unmanaged. commit 00fe0276f1bbfcd19c2a60a5436e6568995c504d Author: Dave Curylo Date: Wed Sep 28 11:54:36 2022 -0400 Adds LinkedUserAssignedIdentity for backwards compatibility commit d28f4eb445a6f7027709df7b6955c12ca72f18ea Author: Ahmed Ebaid Date: Tue Sep 27 12:25:39 2022 -0500 Fix documentation commit 864d5edce8615ddcd7be4c0cd6d31cb753318c7f Author: Ahmed Ebaid Date: Tue Sep 27 07:13:41 2022 -0500 Add additional testing commit 62f59781906577b9cc23091ab649c7744b665b4c Author: Ahmed Ebaid Date: Mon Sep 26 20:38:41 2022 -0500 Fixing documen commit 4061e6361b0e78d093ce1016a0a91ce8b0eb753e Author: Ahmed Ebaid Date: Mon Sep 26 20:10:01 2022 -0500 Run fantomas on changed files commit 652b99c68739023e4a1f92bfade20173a9842c96 Author: Ahmed Ebaid Date: Mon Sep 26 16:44:50 2022 -0500 Adding more testing and updating documentation commit 497494952b84ac05a9dfe7dc29f840993965aa94 Author: Ahmed Ebaid Date: Mon Sep 26 15:29:17 2022 -0500 Removing changes from Common.fs commit b4e09e25141f8ced381df40093650f0b13733ee1 Author: Ahmed Ebaid Date: Mon Sep 26 15:26:19 2022 -0500 Initial work for linking to an identity commit 5733c56143980cb70cc8cb375548a275af5fff3b Author: Dave Curylo Date: Thu Sep 22 11:16:18 2022 -0400 1.7.9 release commit 65b5e79a88fe8546ec3bd090cfdf78fddf8c079e Merge: 043ee73ce 1afcf286f Author: Dave Curylo Date: Wed Sep 21 22:00:34 2022 -0400 Merge pull request #967 from codatio/fix-nic-subnet-rg commit 1afcf286f4d91f85a8224eb2c3684c0f44494614 Merge: d19632161 043ee73ce Author: Richard Sanderson-Pope Date: Wed Sep 21 21:27:35 2022 +0100 merge commit 043ee73ce0d7b7bd4144efcfedb6161e754aea62 Merge: c9c3a9d96 f1c215ec1 Author: Dave Curylo Date: Wed Sep 21 09:43:51 2022 -0400 Merge pull request #977 from ahmed-ebaid/managed_identity_support ACR Managed Identity Support for Container Groups and Container Apps commit f1c215ec1001c55558f6c8e34981ae5909971bf2 Author: Ahmed Ebaid Date: Tue Sep 20 18:08:39 2022 -0500 Format files with fantomas commit 84e99c3e5da892b2c682cf93903c936c7e3160dc Author: Ahmed Ebaid Date: Tue Sep 20 17:32:03 2022 -0500 Updating documentation and release notes commit 81bf829a4e9bca7f3e9fe0d3c59a46f64a197576 Author: Ahmed Ebaid Date: Tue Sep 20 17:30:08 2022 -0500 Uopdate documentation commit 49a12140948b138d013db1180f0fcc16df203b4e Author: Ahmed Ebaid Date: Tue Sep 20 17:18:58 2022 -0500 Fix failing logic commit 5c8b9ba26b1eec4950ed30926c6b8e828e30cd30 Author: Ahmed Ebaid Date: Mon Sep 19 17:13:21 2022 -0500 Modify test to add identity commit 9738e063e9b1968668bc8069264c0fa722270d99 Author: Ahmed Ebaid Date: Mon Sep 19 16:19:47 2022 -0500 Add managed identity support for containers groupd and container apps commit c9c3a9d960b70d9ec56b846125268213acc5e4c8 Merge: 3b1d08e7d ef578a9ec Author: Isaac Abraham Date: Fri Sep 16 11:02:55 2022 +0100 Merge pull request #975 from CompositionalIT/teknikal-wizard-patch-1 Update storage-account.md commit ef578a9ec2d37d56bc6e8389ba904c8636eaf361 Author: Ryan Palmer <66260121+teknikal-wizard@users.noreply.github.com> Date: Fri Sep 16 10:32:01 2022 +0100 Update storage-account.md Added documentation of ip and subnet restriction functionality, addressing https://github.com/CompositionalIT/farmer/issues/951 commit 3b1d08e7d992faf1656a84c8c6cdd6b2cdd513e3 Author: david becher Date: Fri Sep 9 10:14:01 2022 -0700 Update docs to coincide with the last minute change to HopType commit a39dbd898971a79c96a145eedeac403600182740 Author: Dave Curylo Date: Fri Sep 9 10:46:00 2022 -0400 Release 1.7.8 commit 6ab3f7c0e74e97b645afbd4d621f730dd4f7e1ee Author: Dave Curylo Date: Thu Sep 8 23:12:21 2022 -0400 Cleanup resource naming in release notes commit 99f62b461de8027f758be74935eca775436a0318 Author: Dave Curylo Date: Thu Sep 8 23:10:14 2022 -0400 Cleanup release notes commit 04b8ad13b13d69fd68e4f3d58c8e5b8ae31a1c39 Merge: 7c988066d 75c3d0976 Author: Dave Curylo Date: Thu Sep 8 23:07:07 2022 -0400 Merge pull request #969 from codatio/allow-null-priority Allow null priority to allow updating non-spot VM instances commit 7c988066d17a4f2cd8b374a54fbff6574e3986e0 Author: Dave Curylo Date: Thu Sep 8 23:06:27 2022 -0400 Cleanup release notes commit 3c4aa3fafccc076bc890a3bd055abd5dc36ae954 Merge: aa4dafe2d 81ce474f4 Author: Dave Curylo Date: Thu Sep 8 22:50:01 2022 -0400 Merge pull request #972 from thinkdavid/thinkdavid/routeTables Adding support for route tables and their child resource, routes commit 81ce474f41dd299a2f9998eb6e50ae93161c51ba Author: david becher Date: Thu Sep 8 14:06:16 2022 -0700 Making NextHopType include IPAddress if NextHopType is VA commit a06977907eeff1fabc920107d4503383b563f9c4 Author: david becher Date: Thu Sep 8 11:25:10 2022 -0700 formatting ran commit 1aa0f6583cd77e39e9a70dd12eab78872a3e6cee Author: david becher Date: Thu Sep 8 11:22:57 2022 -0700 Document update commit ddcae2ff37b60b1184f0327cd1fbce0bf8ea3929 Author: david becher Date: Wed Sep 7 15:56:03 2022 -0700 Validated in ARM commit a93c2e7c750ff9dc2a8d7e577a481862a3fcda6f Author: david becher Date: Wed Sep 7 15:28:13 2022 -0700 Unit tests passed commit e068e8476415b6dba6f9b909ac8427fa83a9c4b0 Author: david becher Date: Wed Sep 7 13:55:08 2022 -0700 Update Builders.RouteTable.fs commit f4d52f4a5f0c98e4af51095dd7e38b49289eb0b5 Author: david becher Date: Wed Sep 7 13:54:47 2022 -0700 Update Builders.RouteTable.fs commit 394d9b3df498973eb643923daf0d3e904c9a554e Author: david becher Date: Wed Sep 7 13:54:23 2022 -0700 Ready for testing commit df95a0be03aef81695bf8a38671a541aee013e3d Author: david becher Date: Tue Sep 6 17:07:20 2022 -0700 None is reserved for Option commit 01fee70c66f9f442adcb150f21d5ab8dc566028e Author: david becher Date: Tue Sep 6 16:46:22 2022 -0700 Finished enum for NextHopType and added the formatting of the ip address commit 817e92340ac42bec8460ca92a3cb83ee521b11b8 Author: david becher Date: Tue Sep 6 16:38:26 2022 -0700 Minor refactoring commit 903b8ed6226ba887d5cd269b882709cb8f77a850 Author: david becher Date: Tue Sep 6 15:47:21 2022 -0700 Starting code for the route tables addition commit 75c3d09769322cd1ddd9f88476d41aa5e82b4563 Author: Richard Sanderson-Pope Date: Fri Sep 2 14:30:51 2022 +0100 Fix regression tests commit b274b6fef0ec872ea51d3f7341f3fdba509b1ed2 Author: Richard Sanderson-Pope Date: Fri Sep 2 14:13:40 2022 +0100 reformat commit d196321615433d1a53b451586ef7765f483f284c Author: Richard Sanderson-Pope Date: Fri Sep 2 14:11:48 2022 +0100 reformat commit da7c83b4fc9f3bdcc270f6a5c76048a507f0e180 Author: Richard Sanderson-Pope Date: Fri Sep 2 14:09:01 2022 +0100 update docs commit e90b1c330a638154450f7b3392f4fb9ce170caea Author: Richard Sanderson-Pope Date: Fri Sep 2 14:07:21 2022 +0100 Add tests and release notes commit 11590e167bd0672228855104b2a375a25d535729 Author: Richard Sanderson-Pope Date: Fri Sep 2 12:21:15 2022 +0100 Allow null priority to allow updating non-spot VM instances commit 323f62f28af0579b25438264ecb20f8faf3b9e84 Author: Richard Sanderson-Pope Date: Fri Sep 2 11:37:43 2022 +0100 Add release notes commit 162845ee4d7631b3a947174abc682946a4d188ce Author: Richard Sanderson-Pope Date: Fri Sep 2 11:34:04 2022 +0100 Add tests commit 684760c49a5d02dae3fa29af77b59016fe6a2a2f Author: Richard Sanderson-Pope Date: Fri Sep 2 11:15:19 2022 +0100 Keep RG and Subs components from VNet when generating a subnet resourceId --- RELEASE_NOTES.md | 12 + .../api-overview/resources/container-apps.md | 1 + .../api-overview/resources/container-group.md | 5 +- .../api-overview/resources/route-tables.md | 73 +++++++ .../api-overview/resources/storage-account.md | 7 +- .../api-overview/resources/virtual-machine.md | 2 +- src/Farmer/Arm/AVS.fs | 5 + src/Farmer/Arm/App.fs | 28 +++ src/Farmer/Arm/Compute.fs | 14 +- src/Farmer/Arm/ContainerInstance.fs | 14 ++ src/Farmer/Arm/Network.fs | 66 +++++- src/Farmer/Builders/Builders.ContainerApps.fs | 9 + .../Builders/Builders.ContainerGroups.fs | 13 +- src/Farmer/Builders/Builders.RouteTable.fs | 119 ++++++++++ .../Builders/Builders.UserAssignedIdentity.fs | 14 ++ src/Farmer/Builders/Builders.Vm.fs | 7 +- src/Farmer/Common.fs | 37 +++- src/Farmer/Farmer.fsproj | 4 +- src/Farmer/IdentityExtensions.fs | 14 ++ src/Tests/ContainerApps.fs | 5 +- src/Tests/ContainerGroup.fs | 206 ++++++++++++++++-- src/Tests/Network.fs | 85 ++++++++ src/Tests/Tests.fsproj | 2 +- src/Tests/VirtualMachine.fs | 63 ++++++ src/Tests/test-data/lots-of-resources.json | 1 - src/Tests/test-data/vm.json | 1 - 26 files changed, 765 insertions(+), 42 deletions(-) create mode 100644 docs/content/api-overview/resources/route-tables.md create mode 100644 src/Farmer/Arm/AVS.fs create mode 100644 src/Farmer/Builders/Builders.RouteTable.fs diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b07cc1d28..3f73ce47e 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,18 @@ Release Notes ============= +## 1.7.10 +* Container Groups and Container Apps: Support for link_to_identity for ACR managed identities. + +## 1.7.9 +* Container Group: Support for Managed Identity +* Container App: Support for Managed Identity +* VMs: Add support for VNets in other resource groups + +## 1.7.8 +* Route Tables: Initial support for Route Tables and Routes +* Virtual Machines: Default to no priority + ## 1.7.7 * NAT Gateways: Initial support for NAT Gateways. * Private Endpoints: Adds `privateEndpoint` builder and option to set custom network interface name. diff --git a/docs/content/api-overview/resources/container-apps.md b/docs/content/api-overview/resources/container-apps.md index 41c93097e..87d779136 100644 --- a/docs/content/api-overview/resources/container-apps.md +++ b/docs/content/api-overview/resources/container-apps.md @@ -45,6 +45,7 @@ The Container Apps builder (`containerApp`) is used to define one or more contai | active_revision_mode | Indicates whether multiple version of a container app can be active at once.| | add_registry_credentials | Adds container image registry credentials for images in this container app, which are a list of server and usernames. Passwords are supplied as secure parameters. | | reference_registry_credentials | Adds container image registry credentials for images in this container app in the form of a list of Azure resource ids. | +| add_managed_identity_registry_credentials | Adds container app registry managed identity credentials for images in this container app, which are a list of server and identities. | | add_containers | Adds a list of containers to this container app. All containers in the app share resources and scaling. | | add_simple_container | Adds a single container that references a public docker image and version. | | add_secret_parameter | Adds an application secret to the entire container app. This is passed as a secure parameter to the template, and an environment variable is automatically created which references the secret. | diff --git a/docs/content/api-overview/resources/container-group.md b/docs/content/api-overview/resources/container-group.md index 754091f2d..d9d017196 100644 --- a/docs/content/api-overview/resources/container-group.md +++ b/docs/content/api-overview/resources/container-group.md @@ -27,9 +27,11 @@ The Container Group builder (`containerGroup`) defines a Container Group. | link_to_vnet | Resource ID of an existing virtual network where the container group will attach. | | subnet | Name of the subnet in a virtual network where the container group will attach. | | add_identity | Adds a managed identity to the the container group. | +| link_to_identity | Links an existent managed identity to the container group. | | system_identity | Activates the system identity of the container group. | | add_registry_credentials | Adds a container image registry credential with a secure parameter for the password. | | reference_registry_credentials | References credentials from a container image registry by resource ID. | +| add_managed_identity_registry_credentials | Adds container image registry managed identity credentials for images in this container group. | | add_tcp_port | Adds a TCP port to be externally accessible. | | add_udp_port | Adds a UDP port to be externally accessible. | | add_volumes | Adds volumes to a container group so they are accessible to containers. | @@ -151,6 +153,7 @@ let containerGroupUser = userAssignedIdentity { } let containerGroupLoggingWorkspace = logAnalytics { name "webapplogs" } +let managedIdentity = ManagedIdentity.Empty let group = containerGroup { name "webApp" @@ -162,7 +165,7 @@ let group = containerGroup { add_instances [ nginx ] // Add registry credentials as a secure password add_registry_credentials [ - registry "mygregistry.azurecr.io" "registryuser" + registry "mygregistry.azurecr.io" "registryuser" managedIdentity ] // or reference an Azure container registry to pull the credentials directly. reference_registry_credentials [ diff --git a/docs/content/api-overview/resources/route-tables.md b/docs/content/api-overview/resources/route-tables.md new file mode 100644 index 000000000..753ddbea1 --- /dev/null +++ b/docs/content/api-overview/resources/route-tables.md @@ -0,0 +1,73 @@ +--- +title: "Route Tables" +date: 2022-09-08T22:26:00-04:00 +chapter: false +weight: 5 +--- + +#### Overview +The `routeTable` builder creates a route table to efficiently change default routing traffic between Azure subnets, virtual networks, and on-premises networks. To learn more about routeTables, reference the [Azure Docs](https://docs.microsoft.com/en-us/azure/virtual-network/manage-route-table) + +* RouteTable (`Microsoft.Network/routeTables`) +* Route (`Microsoft.Network/routeTables/routes`) + +#### Builder Keywords + +| Applies To | Keyword | Purpose | +|-|-|-| +| routeTable | name | Name of the NAT Gateway resource | +| routeTable | disableBgpRoutePropagation | Whether to disable the routes learned by BGP on that route table | +| routeTable | add_routes | The routes to be added to this route table | +| route | name | Name of the route resource | +| route | addressPrefix | The destination CIDR to which the route applies | +| route | nextHopType | The type of Azure hop the packet should be sent to | +| route | nextHopIpAddress | The IP address packets should be forwarded to. Only allowed in routes where the next hop type is VirtualAppliance | +| route | hasBgpOverride | Whether the route overrides overalpping BGP routes regardless of LPM | + +#### Example + +```fsharp +#r "nuget:Farmer" + +open Farmer +open Farmer.Builders + +arm { + location Location.EastUS + + add_resources + [ + routeTable { + name "myroutetable" + + add_routes + [ + route { + name "myroute" + addressPrefix "10.10.90.0/24" + nextHopIpAddress "10.10.67.5" + } + route { + name "myroute2" + addressPrefix "10.10.80.0/24" + } + route { + name "myroute3" + addressPrefix "10.2.31.0/24" + nextHopType (Route.HopType.VirtualAppliance None) + } + route { + name "myroute4" + addressPrefix "10.2.31.0/24" + + nextHopType ( + Route.HopType.VirtualAppliance( + Some(System.Net.IPAddress.Parse "10.2.31.2") + ) + ) + } + ] + } + ] +} +``` \ No newline at end of file diff --git a/docs/content/api-overview/resources/storage-account.md b/docs/content/api-overview/resources/storage-account.md index 95cb42c5a..59e34565f 100644 --- a/docs/content/api-overview/resources/storage-account.md +++ b/docs/content/api-overview/resources/storage-account.md @@ -34,6 +34,8 @@ The Storage Account builder creates storage accounts and their associated contai | add_cors_rules | Adds a list of CORS rules to the different storage services | | add_policies | Adds a list of Policies to the different storage services | | enable_versioning | Enabled versioning for different storage services | +| restrict_to_ip | Restrict access to a given ip address | +| restrict_to_subnet | Restrict access to a given virtual network subnet | | use_static_website | Activates static website host, and uploads the provided local content as a post-deployment task to the storage with the specified index page | | static_website_error_page | Specifies the 404 page to display for static website hosting | | enable_data_lake | Enables Azure Data Lake Gen2 support on the storage account | @@ -63,6 +65,9 @@ open Farmer.Storage let storage = storageAccount { name "isaacssuperstorage" sku Storage.Sku.Premium_LRS + restrict_to_ip "11.22.33.44" + restrict_to_ip "12.23.45.78" + restrict_to_subnet "myvnet" "mysubnet" add_public_container "mypubliccontainer" add_private_container "myprivatecontainer" add_blob_container "myblobcontainer" @@ -94,4 +99,4 @@ let storage = storageAccount { enable_versioning [ StorageService.Blobs, true ] min_tls_version Tls12 } -``` \ No newline at end of file +``` diff --git a/docs/content/api-overview/resources/virtual-machine.md b/docs/content/api-overview/resources/virtual-machine.md index ca70ec838..4b22d8624 100644 --- a/docs/content/api-overview/resources/virtual-machine.md +++ b/docs/content/api-overview/resources/virtual-machine.md @@ -25,7 +25,7 @@ In addition, every VM you create will add a SecureString parameter to the ARM te |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.| |vm_size|Sets the size of the VM.| -|priority|Sets the VM Priority. Only one `spot_instance` or `priority` setting is allowed per 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.| |username|Sets the admin username of the VM (note: the password is supplied as a securestring parameter to the generated ARM template).| |password_parameter|Sets the name of the parameter which contains the admin password for this VM. defaults to "password-for-"| diff --git a/src/Farmer/Arm/AVS.fs b/src/Farmer/Arm/AVS.fs new file mode 100644 index 000000000..d027dc24f --- /dev/null +++ b/src/Farmer/Arm/AVS.fs @@ -0,0 +1,5 @@ +[] +module Farmer.Arm.AVS + +open Farmer +let privateClouds = ResourceType("Microsoft.AVS/privateClouds", "2021-12-01") diff --git a/src/Farmer/Arm/App.fs b/src/Farmer/Arm/App.fs index 35cc22590..ac67f275b 100644 --- a/src/Farmer/Arm/App.fs +++ b/src/Farmer/Arm/App.fs @@ -1,6 +1,7 @@ [] module Farmer.Arm.App +open System open Farmer.ContainerApp open Farmer.Identity open Farmer @@ -122,6 +123,8 @@ type ContainerApp = match credential with | ImageRegistryAuthentication.Credential credential -> credential.Password | ImageRegistryAuthentication.ListCredentials _ -> () + | ImageRegistryAuthentication.ManagedIdentityCredential _ -> () + ] interface IArmResource with @@ -160,6 +163,18 @@ type ContainerApp = $"listCredentials({resourceId.ArmExpression.Value}, '2019-05-01').passwords[0].value" ) .Eval() + |} + | ImageRegistryAuthentication.ManagedIdentityCredential cred -> + {| + name = cred.Server + value = + if cred.Identity.Dependencies.Length > 0 then + cred.Identity.Dependencies.Head.ArmExpression.Eval() + else + String.Empty + + + |} for setting in this.Secrets do {| @@ -180,6 +195,7 @@ type ContainerApp = server = cred.Server username = cred.Username passwordSecretRef = cred.Username + identity = null |} | ImageRegistryAuthentication.ListCredentials resourceId -> {| @@ -191,6 +207,18 @@ type ContainerApp = ) .Eval() passwordSecretRef = usernameSecretName resourceId + identity = null + |} + | ImageRegistryAuthentication.ManagedIdentityCredential cred -> + {| + server = cred.Server + username = String.Empty + passwordSecretRef = null + identity = + if cred.Identity.Dependencies.Length > 0 then + cred.Identity.Dependencies.Head.ArmExpression.Eval() + else + String.Empty |} |] ingress = diff --git a/src/Farmer/Arm/Compute.fs b/src/Farmer/Arm/Compute.fs index b1df5fedc..fd14299c6 100644 --- a/src/Farmer/Arm/Compute.fs +++ b/src/Farmer/Arm/Compute.fs @@ -101,7 +101,7 @@ type VirtualMachine = DiagnosticsEnabled: bool option StorageAccount: ResourceName option Size: VMSize - Priority: Priority + Priority: Priority option Credentials: {| Username: string Password: SecureParameter |} CustomData: string option @@ -137,7 +137,10 @@ type VirtualMachine = let properties = {| - priority = this.Priority.ArmValue + priority = + match this.Priority with + | Some priority -> priority.ArmValue + | _ -> Unchecked.defaultof<_> hardwareProfile = {| vmSize = this.Size.ArmValue |} osProfile = {| @@ -260,9 +263,10 @@ type VirtualMachine = this.Identity.ToArmJson properties = match this.Priority with - | Low - | Regular -> box properties - | Spot (evictionPolicy, maxPrice) -> + | None + | Some Low + | Some Regular -> box properties + | Some (Spot (evictionPolicy, maxPrice)) -> {| properties with evictionPolicy = evictionPolicy.ArmValue billingProfile = {| maxPrice = maxPrice |} diff --git a/src/Farmer/Arm/ContainerInstance.fs b/src/Farmer/Arm/ContainerInstance.fs index bc2c5dda0..5ee45dd84 100644 --- a/src/Farmer/Arm/ContainerInstance.fs +++ b/src/Farmer/Arm/ContainerInstance.fs @@ -207,6 +207,7 @@ type ContainerGroup = match credential with | ImageRegistryAuthentication.Credential credential -> credential.Password | ImageRegistryAuthentication.ListCredentials _ -> () + | ImageRegistryAuthentication.ManagedIdentityCredential _ -> () for container in this.ContainerInstances do for envVar in container.EnvironmentVariables do match envVar.Value with @@ -365,6 +366,7 @@ type ContainerGroup = server = cred.Server username = cred.Username password = cred.Password.ArmExpression.Eval() + identity = null |} | ImageRegistryAuthentication.ListCredentials resourceId -> {| @@ -386,6 +388,18 @@ type ContainerGroup = $"listCredentials({resourceId.ArmExpression.Value}, '2019-05-01').passwords[0].value" ) .Eval() + identity = null + |} + | ImageRegistryAuthentication.ManagedIdentityCredential cred -> + {| + server = cred.Server + username = String.Empty + password = null + identity = + cred.Identity.UserAssigned + |> List.tryHead + |> Option.map (fun upn -> upn.ResourceId.ArmExpression.Eval()) + |> Option.defaultValue null |}) ipAddress = match this.IpAddress with diff --git a/src/Farmer/Arm/Network.fs b/src/Farmer/Arm/Network.fs index afdb46e1d..803c9942b 100644 --- a/src/Farmer/Arm/Network.fs +++ b/src/Farmer/Arm/Network.fs @@ -5,6 +5,7 @@ open System.Net.Mail open Farmer open Farmer.Arm open Farmer.ExpressRoute +open Farmer.Route open Farmer.VirtualNetworkGateway let connections = ResourceType("Microsoft.Network/connections", "2020-04-01") @@ -50,6 +51,10 @@ let privateEndpoints = let virtualNetworkPeering = ResourceType("Microsoft.Network/virtualNetworks/virtualNetworkPeerings", "2020-05-01") +let routeTables = ResourceType("Microsoft.Network/routeTables", "2021-01-01") +let routes = ResourceType("Microsoft.Network/routeTables/routes", "2021-01-01") + + type SubnetReference = | ViaManagedVNet of (ResourceId * ResourceName) | Direct of LinkedResource @@ -90,6 +95,59 @@ type SubnetReference = Direct subnetRef +type Route = + { + Name: ResourceName + AddressPrefix: IPAddressCidr + NextHopType: Route.HopType + HasBgpOverride: FeatureFlag + } + + member internal this.JsonModelProperties = + {| + addressPrefix = IPAddressCidr.format this.AddressPrefix + nextHopType = this.NextHopType.ArmValue + nextHopIpAddress = + match this.NextHopType with + | VirtualAppliance ip -> + ip + |> Option.map (fun x -> x.ToString()) + |> Option.defaultValue Unchecked.defaultof<_> + | _ -> Unchecked.defaultof<_> + hasBgpOverride = this.HasBgpOverride.AsBoolean + |} + + interface IArmResource with + member this.ResourceId = routes.resourceId this.Name + + member this.JsonModel = + {| routes.Create(this.Name) with + properties = this.JsonModelProperties + |} + +type RouteTable = + { + Name: ResourceName + Location: Location + Tags: Map + DisableBGPRoutePropagation: FeatureFlag + Routes: Route list + } + + member internal this.JsonModelProperties = + {| + disableBgpRoutePropagation = this.DisableBGPRoutePropagation.AsBoolean + routes = this.Routes |> Seq.map (fun x -> (x :> IArmResource).JsonModel) + |} + + interface IArmResource with + member this.ResourceId = routeTables.resourceId this.Name + + member this.JsonModel = + {| routeTables.Create(this.Name, this.Location, tags = this.Tags) with + properties = this.JsonModelProperties + |} + type PublicIpAddress = { Name: ResourceName @@ -550,8 +608,10 @@ type NetworkInterface = subnet = {| id = - subnets - .resourceId(this.VirtualNetwork.Name, ipConfig.SubnetName) + { this.VirtualNetwork.ResourceId with + Type = subnets + Segments = [ ipConfig.SubnetName ] + } .Eval() |} |} @@ -559,6 +619,8 @@ type NetworkInterface = + + |}) |} diff --git a/src/Farmer/Builders/Builders.ContainerApps.fs b/src/Farmer/Builders/Builders.ContainerApps.fs index c40e410c4..f4bb35a54 100644 --- a/src/Farmer/Builders/Builders.ContainerApps.fs +++ b/src/Farmer/Builders/Builders.ContainerApps.fs @@ -409,6 +409,15 @@ type ContainerAppBuilder() = @ (resourceIds |> List.map ImageRegistryAuthentication.ListCredentials) } + /// Adds container app registry managed identity credentials for images in this container app. + [] + member _.ManagedIdentityRegistryCredentials(state: ContainerAppConfig, credentials) = + { state with + ImageRegistryCredentials = + state.ImageRegistryCredentials + @ (credentials |> List.map ImageRegistryAuthentication.ManagedIdentityCredential) + } + /// Adds one or more containers to the container app. [] member _.AddContainers(state: ContainerAppConfig, containers: ContainerConfig list) = diff --git a/src/Farmer/Builders/Builders.ContainerGroups.fs b/src/Farmer/Builders/Builders.ContainerGroups.fs index 0edadcad2..27a2be1f3 100644 --- a/src/Farmer/Builders/Builders.ContainerGroups.fs +++ b/src/Farmer/Builders/Builders.ContainerGroups.fs @@ -2,6 +2,7 @@ module Farmer.Builders.ContainerGroups open Farmer +open Farmer.Arm open Farmer.Builders open Farmer.ContainerGroup open Farmer.Identity @@ -453,6 +454,15 @@ type ContainerGroupBuilder() = @ (resourceIds |> List.map ImageRegistryAuthentication.ListCredentials) } + /// Adds container image registry managed identity credentials for images in this container group. + [] + member _.ManagedIdentityRegistryCredentials(state: ContainerGroupConfig, credentials) = + { state with + ImageRegistryCredentials = + state.ImageRegistryCredentials + @ (credentials |> List.map ImageRegistryAuthentication.ManagedIdentityCredential) + } + /// Adds a collection of init containers to this group that run once on startup before other containers in the group. [] member _.AddInitContainers(state: ContainerGroupConfig, initContainers) = @@ -610,11 +620,12 @@ type ContainerGroupBuilder() = } /// Creates an image registry credential with a generated SecureParameter for the password. -let registry (server: string) (username: string) = +let registry (server: string) (username: string) (managedIdentity: ManagedIdentity) = { Server = server Username = username Password = SecureParameter $"{server}-password" + Identity = managedIdentity } type ContainerInstanceBuilder() = diff --git a/src/Farmer/Builders/Builders.RouteTable.fs b/src/Farmer/Builders/Builders.RouteTable.fs new file mode 100644 index 000000000..4a91e613e --- /dev/null +++ b/src/Farmer/Builders/Builders.RouteTable.fs @@ -0,0 +1,119 @@ +[] +module Farmer.Builders.RouteTable + +open Farmer +open Farmer.Arm +open Farmer.Route + +type RouteConfig = + { + Name: ResourceName + AddressPrefix: IPAddressCidr option + NextHopType: Route.HopType + HasBgpOverride: FeatureFlag option + } + +type RouteTableConfig = + { + Name: ResourceName + DisableBGPRoutePropagation: FeatureFlag option + Routes: RouteConfig list + Tags: Map + } + + interface IBuilder with + member this.ResourceId = routeTables.resourceId this.Name + + member this.BuildResources location = + let routes: Network.Route list = + this.Routes + |> List.map (fun r -> + match r.AddressPrefix with + | None -> raiseFarmer ("address prefix is required") + | Some addressPrefix -> + { + Name = r.Name + AddressPrefix = addressPrefix + NextHopType = r.NextHopType + HasBgpOverride = r.HasBgpOverride |> Option.defaultValue FeatureFlag.Disabled + }) + + let routeTable: Network.RouteTable = + { + RouteTable.Name = this.Name + Location = location + DisableBGPRoutePropagation = + this.DisableBGPRoutePropagation |> Option.defaultValue FeatureFlag.Disabled + Routes = routes + Tags = this.Tags + } + + [ routeTable ] + +type RouteTableBuilder() = + member _.Yield _ = + { + Name = ResourceName.Empty + Routes = [] + DisableBGPRoutePropagation = None + Tags = Map.empty + } + + [] + member _.Name(state: RouteTableConfig, name: string) = { state with Name = ResourceName name } + + [] + member _.DisableBGPRoutePropagation(state: RouteTableConfig, flag: bool) = + { state with + DisableBGPRoutePropagation = Some(FeatureFlag.ofBool flag) + } + + [] + member _.AddRoute(state: RouteTableConfig, routeConfigs: RouteConfig list) = + { state with + Routes = routeConfigs @ state.Routes + } + +let routeTable = RouteTableBuilder() + +type RouteBuilder() = + member _.Yield _ = + { + Name = ResourceName.Empty + AddressPrefix = None + NextHopType = Route.HopType.Nothing + HasBgpOverride = None + } + + [] + member _.Name(state: RouteConfig, name: string) = { state with Name = ResourceName name } + + [] + member _.AddressPrefix(state: RouteConfig, ip: IPAddressCidr) = { state with AddressPrefix = Some ip } + + member _.AddressPrefix(state: RouteConfig, ip: string) = + { state with + AddressPrefix = Some(IPAddressCidr.parse ip) + } + + [] + member _.NextHopType(state: RouteConfig, ht: Route.HopType) = { state with NextHopType = ht } + + [] + member _.NextHopIpAddress(state: RouteConfig, ip: System.Net.IPAddress) = + { state with + NextHopType = VirtualAppliance(Some ip) + } + + member _.NextHopIpAddress(state: RouteConfig, ip: string) = + { state with + NextHopType = VirtualAppliance(Some(System.Net.IPAddress.Parse ip)) + } + + [] + member _.HasBgpOverride(state: RouteConfig, flag: bool) = + { state with + HasBgpOverride = Some(FeatureFlag.ofBool flag) + } + +let route = RouteBuilder() diff --git a/src/Farmer/Builders/Builders.UserAssignedIdentity.fs b/src/Farmer/Builders/Builders.UserAssignedIdentity.fs index 2bfaa3f0f..5d17c52b9 100644 --- a/src/Farmer/Builders/Builders.UserAssignedIdentity.fs +++ b/src/Farmer/Builders/Builders.UserAssignedIdentity.fs @@ -24,7 +24,9 @@ type UserAssignedIdentityConfig = ] member this.ResourceId = userAssignedIdentities.resourceId this.Name + member this.UserAssignedIdentity = UserAssignedIdentity this.ResourceId + member this.ClientId = this.UserAssignedIdentity.ClientId member this.PrincipalId = this.UserAssignedIdentity.PrincipalId @@ -65,6 +67,18 @@ module Extensions = member this.AddIdentity(state, identity: UserAssignedIdentityConfig) = this.AddIdentity(state, identity.UserAssignedIdentity) + member this.AddIdentity(state: 'TConfig, resourceId: ResourceId) = + let userAssignedIdentity = UserAssignedIdentity resourceId + this.Add state (fun current -> current + userAssignedIdentity) + + [] + member this.LinkToIdentity(state: 'TConfig, resourceId: ResourceId) = + let userAssignedIdentity = LinkedUserAssignedIdentity resourceId + this.Add state (fun current -> current + userAssignedIdentity) + + member this.LinkToIdentity(state, identity: UserAssignedIdentityConfig) = + this.LinkToIdentity(state, identity.ResourceId) + [] member this.SystemIdentity(state: 'TConfig) = this.Add state (fun current -> diff --git a/src/Farmer/Builders/Builders.Vm.fs b/src/Farmer/Builders/Builders.Vm.fs index 7c0a3da3b..d6ef0ccaf 100644 --- a/src/Farmer/Builders/Builders.Vm.fs +++ b/src/Farmer/Builders/Builders.Vm.fs @@ -87,7 +87,7 @@ type VmConfig = StorageAccount = this.DiagnosticsStorageAccount |> Option.map (fun r -> r.resourceId(this).Name) NetworkInterfaceName = this.NicName.Name Size = this.Size - Priority = this.Priority |> Option.defaultValue Regular + Priority = this.Priority Credentials = match this.Username with | Some username -> @@ -442,6 +442,11 @@ type VirtualMachineBuilder() = member this.LinkToVNet(state: VmConfig, vnet: VirtualNetworkConfig) = this.LinkToVNet(state, vnet.Name) [] + member _.LinkToUnmanagedVNet(state: VmConfig, id: ResourceId) = + { state with + VNet = LinkedResource(Unmanaged(id)) + } + member _.LinkToUnmanagedVNet(state: VmConfig, name: ResourceName) = { state with VNet = LinkedResource(Unmanaged(virtualNetworks.resourceId name)) diff --git a/src/Farmer/Common.fs b/src/Farmer/Common.fs index e46cc27c7..3895bdf86 100644 --- a/src/Farmer/Common.fs +++ b/src/Farmer/Common.fs @@ -151,6 +151,22 @@ type EnvVar = module Mb = let toBytes (mb: int) = int64 mb * 1024L * 1024L +module Route = + type HopType = + | VirtualAppliance of System.Net.IPAddress option + | Internet + | Nothing + | VirtualNetworkGateway + | VnetLocal + + member x.ArmValue = + match x with + | VirtualAppliance _ -> "VirtualAppliance" + | Internet -> "Internet" + | Nothing -> "None" + | VirtualNetworkGateway -> "VirtualNetworkGateway" + | VnetLocal -> "VnetLocal" + module Vm = type VMSize = | Basic_A0 @@ -1818,9 +1834,13 @@ module Identity = /// Represents a User Assigned Identity, and the ability to create a Principal Id from it. type UserAssignedIdentity = | UserAssignedIdentity of ResourceId + | LinkedUserAssignedIdentity of ResourceId member private this.CreateExpression field = - let (UserAssignedIdentity resourceId) = this + let resourceId = + match this with + | UserAssignedIdentity rid -> rid + | LinkedUserAssignedIdentity rid -> rid ArmExpression .create($"reference({resourceId.ArmExpression.Value}).%s{field}") @@ -1831,7 +1851,8 @@ module Identity = member this.ResourceId = match this with - | UserAssignedIdentity r -> r + | UserAssignedIdentity rid -> rid + | LinkedUserAssignedIdentity rid -> rid type SystemIdentity = | SystemIdentity of ResourceId @@ -1857,7 +1878,12 @@ module Identity = UserAssigned: UserAssignedIdentity list } - member this.Dependencies = this.UserAssigned |> List.map (fun u -> u.ResourceId) + member this.Dependencies = + this.UserAssigned + |> List.choose (fun identity -> + match identity with + | UserAssignedIdentity rid -> Some rid + | LinkedUserAssignedIdentity _ -> None) static member Empty = { @@ -1876,6 +1902,8 @@ module Identity = UserAssigned = userAssignedIdentity :: managedIdentity.UserAssigned } +open Identity + module Containers = type DockerImage = | PrivateImage of RegistryDomain: string * ContainerName: string * Version: string option @@ -1911,6 +1939,7 @@ type ImageRegistryCredential = Server: string Username: string Password: SecureParameter + Identity: ManagedIdentity } [] @@ -1919,6 +1948,8 @@ type ImageRegistryAuthentication = | Credential of ImageRegistryCredential /// Credentials for the container registry will be listed by ARM expression. | ListCredentials of ResourceId + /// Credentials for the container registry are included with the identity as a template parameter. + | ManagedIdentityCredential of ImageRegistryCredential [] type LogAnalyticsWorkspace = diff --git a/src/Farmer/Farmer.fsproj b/src/Farmer/Farmer.fsproj index 2c2ef1441..8ec6687e8 100644 --- a/src/Farmer/Farmer.fsproj +++ b/src/Farmer/Farmer.fsproj @@ -2,7 +2,7 @@ Farmer - 1.7.7 + 1.7.10 Farmer makes authoring ARM templates easy! Copyright 2019-2022 Compositional IT Ltd. Compositional IT @@ -77,6 +77,7 @@ + @@ -170,6 +171,7 @@ + diff --git a/src/Farmer/IdentityExtensions.fs b/src/Farmer/IdentityExtensions.fs index 1ec1f5612..d320c4b72 100644 --- a/src/Farmer/IdentityExtensions.fs +++ b/src/Farmer/IdentityExtensions.fs @@ -1,5 +1,6 @@ namespace Farmer +open Farmer.Arm open Identity open Farmer.Arm.ManagedIdentity open System @@ -15,6 +16,19 @@ module ManagedIdentityExtensions = UserAssigned = [ UserAssignedIdentity resourceId ] } + static member create(identity: Identity.UserAssignedIdentity) = + match identity with + | LinkedUserAssignedIdentity rid -> + { + SystemAssigned = Disabled + UserAssigned = [ LinkedUserAssignedIdentity rid ] + } + | UserAssignedIdentity rid -> + { + SystemAssigned = Disabled + UserAssigned = [ UserAssignedIdentity rid ] + } + /// Creates a resource identity from a resource name static member create(name: ResourceName) = userAssignedIdentities.resourceId name |> ManagedIdentity.create diff --git a/src/Tests/ContainerApps.fs b/src/Tests/ContainerApps.fs index 09d583d13..2a3b56f0d 100644 --- a/src/Tests/ContainerApps.fs +++ b/src/Tests/ContainerApps.fs @@ -23,6 +23,7 @@ let fullContainerAppDeployment = } let version = "1.0.0" + let managedIdentity = ManagedIdentity.Empty let containerEnv = containerEnvironment { @@ -35,7 +36,9 @@ let fullContainerAppDeployment = name "http" add_identity msi active_revision_mode Single - add_registry_credentials [ registry containerRegistryDomain containerRegistryName ] + + add_registry_credentials + [ registry containerRegistryDomain containerRegistryName managedIdentity ] add_containers [ diff --git a/src/Tests/ContainerGroup.fs b/src/Tests/ContainerGroup.fs index 7dfa2f2b1..7143a6671 100644 --- a/src/Tests/ContainerGroup.fs +++ b/src/Tests/ContainerGroup.fs @@ -5,7 +5,6 @@ open Farmer open Farmer.Arm open Farmer.Identity open Farmer.ContainerGroup -open Farmer.Arm.ContainerInstance open Farmer.Builders open Farmer.Network open Microsoft.Azure.Management.ContainerInstance @@ -73,11 +72,6 @@ let tests = Expect.equal group.OsType "Linux" "OS should be Linux" Expect.equal group.IpAddress.Ports.[1].PortProperty 123 "Incorrect udp port" - Expect.equal - group.NetworkProfile.Id - "[resourceId('Microsoft.Network/networkProfiles', 'test')]" - "Incorrect network profile reference" - let containerInstance = group.Containers.[0] Expect.equal containerInstance.Image "nginx:1.17.6-alpine" "Incorrect image" Expect.equal containerInstance.Name "nginx" "Incorrect instance name" @@ -271,10 +265,12 @@ let tests = } test "Container group with private registry" { + let managedIdentity = ManagedIdentity.Empty + let group = containerGroup { add_instances [ nginx ] - add_registry_credentials [ registry "my-registry.azurecr.io" "user" ] + add_registry_credentials [ registry "my-registry.azurecr.io" "user" managedIdentity ] } |> asAzureResource @@ -289,6 +285,96 @@ let tests = "Container image registry password should be secure parameter" } + test "Container group with managed identity to private registry" { + let userAssignedIdentity = + ResourceId.create (Arm.ManagedIdentity.userAssignedIdentities, ResourceName "user", "resourceGroup") + |> UserAssignedIdentity + + let managedIdentity = + { ManagedIdentity.Empty with + UserAssigned = [ userAssignedIdentity ] + } + + let group = + containerGroup { + add_instances [ nginx ] + + add_identity ( + ResourceId.create ( + Arm.ManagedIdentity.userAssignedIdentities, + ResourceName "user", + "resourceGroup" + ) + |> UserAssignedIdentity + ) + + add_managed_identity_registry_credentials + [ registry "my-registry.azurecr.io" "user" managedIdentity ] + } + |> asAzureResource + + Expect.hasLength + group.ImageRegistryCredentials + 1 + "Expected one image managed identity registry credential" + + let credentials = group.ImageRegistryCredentials.[0] + Expect.equal credentials.Server "my-registry.azurecr.io" "Incorrect container image registry server" + Expect.equal credentials.Username String.Empty "Container image registry user should be null" + + Expect.equal + credentials.Identity + (managedIdentity.Dependencies.Head.ArmExpression.Eval()) + "Incorrect container image registry identity" + + Expect.equal credentials.Password null "Container image registry password should be null" + } + + test "Container group with an link_to_identity to private registry" { + let resourceId = + ResourceId.create (ManagedIdentity.userAssignedIdentities, ResourceName "user", "resourceGroup") + + let managedIdentity = + { ManagedIdentity.Empty with + UserAssigned = [ (LinkedUserAssignedIdentity resourceId) ] + } + + let containerGroupConfig = + containerGroup { + add_instances [ nginx ] + link_to_identity resourceId + + add_managed_identity_registry_credentials + [ registry "my-registry.azurecr.io" "user" managedIdentity ] + } + + let group = containerGroupConfig |> asAzureResource + + Expect.hasLength + group.ImageRegistryCredentials + 1 + "Expected one image managed identity registry credential" + + let credentials = group.ImageRegistryCredentials.[0] + Expect.equal credentials.Server "my-registry.azurecr.io" "Incorrect container image registry server" + Expect.equal credentials.Username String.Empty "Container image registry user should be null" + + Expect.equal + credentials.Identity + (managedIdentity.UserAssigned.Head.ResourceId.ArmExpression.Eval()) + "Incorrect container image registry identity" + + Expect.notEqual credentials.Identity null "Identity should not be null" + Expect.notEqual credentials.Identity String.Empty "Identity should not be an empty string" + + Expect.equal + containerGroupConfig.Identity.Dependencies.Length + 0 + "Container Group Config Identity Dependencies should be 0" + + Expect.equal credentials.Password null "Container image registry password should be null" + } + test "Container group with reference to private registry" { let group = containerGroup { @@ -833,7 +919,7 @@ async { link_to_vnet ( ResourceId.create ( - Arm.Network.virtualNetworks, + virtualNetworks, (ResourceName "containerNet"), group = "other-res-group" ) @@ -889,21 +975,7 @@ async { Expect.equal (string ipConfigName) "ipconfigProfile" "netprofile ipConfiguration has wrong name" } - test "Can link a network profile directly to a container group" { - let profile = networkProfile { name "netprofile" } - let template = - containerGroup { - name "appWithHttpFrontend" - network_profile profile - } - |> asAzureResource - - Expect.equal - "[resourceId('Microsoft.Network/networkProfiles', 'netprofile')]" - template.NetworkProfile.Id - "Incorrect profile name" - } test "Support for additional dependencies" { let storage = storageAccount { name "containerstorage" } @@ -1192,4 +1264,94 @@ async { Expect.sequenceEqual nameservers [ JValue "8.8.8.8"; JValue "1.1.1.1" ] "Incorrect nameservers." Expect.equal searchDomains (JValue "example.com example.local") "Incorrect search domains." } + + test "Create container group created with a link_to_identity" { + let resourceId = + ResourceId.create (ManagedIdentity.userAssignedIdentities, ResourceName "user", "resourceGroup") + + let managedIdentity: Identity.ManagedIdentity = + { ManagedIdentity.Empty with + UserAssigned = [ (LinkedUserAssignedIdentity resourceId) ] + } + + let containerGroup = + containerGroup { + name "container-group-with-link-to-identity" + link_to_identity resourceId + + add_managed_identity_registry_credentials + [ registry "my-registry.azurecr.io" "user" managedIdentity ] + + add_instances + [ + containerInstance { + name "httpserver" + image "nginx:1.17.6-alpine" + } + ] + } + + let deployment = + arm { + add_resources + [ + containerGroup + + ] + } + + let jobj = deployment.Template |> Writer.toJson |> JObject.Parse + + let containerGroupJson = + jobj.SelectToken("resources[?(@.name=='container-group-with-link-to-identity')]") + + let dependsOn = containerGroupJson.SelectToken("dependsOn") :?> JArray + Expect.equal dependsOn.Count 0 "Container group dependsOn list shall be empty" + } + + test "Create container group created with a add_identity" { + let resourceId = + ResourceId.create (ManagedIdentity.userAssignedIdentities, ResourceName "user", "resourceGroup") + + let userAssignedIdentity = resourceId |> UserAssignedIdentity + + let managedIdentity: Identity.ManagedIdentity = + { ManagedIdentity.Empty with + UserAssigned = [ userAssignedIdentity ] + } + + let containerGroup = + containerGroup { + name "container-group-with-add-identity" + add_identity userAssignedIdentity + + add_managed_identity_registry_credentials + [ registry "my-registry.azurecr.io" "user" managedIdentity ] + + add_instances + [ + containerInstance { + name "httpserver" + image "nginx:1.17.6-alpine" + } + ] + } + + let deployment = + arm { + add_resources + [ + containerGroup + + ] + } + + let jobj = deployment.Template |> Writer.toJson |> JObject.Parse + + let containerGroupJson = + jobj.SelectToken("resources[?(@.name=='container-group-with-add-identity')]") + + let dependsOn = containerGroupJson.SelectToken("dependsOn") :?> JArray + Expect.equal dependsOn.Count 1 "Container group dependsOn list shouldn't be empty" + } ] diff --git a/src/Tests/Network.fs b/src/Tests/Network.fs index 9e916562c..01b03aa9b 100644 --- a/src/Tests/Network.fs +++ b/src/Tests/Network.fs @@ -664,6 +664,91 @@ let tests = Expect.isNotNull publicIp "Public IP should have been generated for the NAT gateway." } + test "Creates route table with two routes" { + let deployment = + arm { + location Location.EastUS + + add_resources + [ + routeTable { + name "myroutetable" + + add_routes + [ + route { + name "myroute" + addressPrefix "10.10.90.0/24" + nextHopIpAddress "10.10.67.5" + } + route { + name "myroute2" + addressPrefix "10.10.80.0/24" + } + route { + name "myroute3" + addressPrefix "10.2.31.0/24" + nextHopType (Route.HopType.VirtualAppliance None) + } + route { + name "myroute4" + addressPrefix "10.2.31.0/24" + + nextHopType ( + Route.HopType.VirtualAppliance( + Some(System.Net.IPAddress.Parse "10.2.31.2") + ) + ) + } + ] + } + ] + } + + let jobj = deployment.Template |> Writer.toJson |> JObject.Parse + let a = jobj.ToString() + + let routeTable = + jobj.SelectToken "resources[?(@.type=='Microsoft.Network/routeTables')]" + + let routeTableProps = routeTable.["properties"] + + let disableBgp: bool = + JToken.op_Explicit routeTableProps.["disableBgpRoutePropagation"] + + Expect.equal disableBgp false "Incorrect default value for disableBgpRoutePropagation" + let routes = routeTableProps.["routes"] :?> JArray + Expect.isNotNull routes "Routes should have been generated for the route table" + Expect.equal (string routes.[0].["name"]) "myroute" "route 1 should be named 'myroute'" + Expect.equal (string routes.[1].["name"]) "myroute2" "route 2 should be named 'myroute2'" + let routeProps = routes.[0].["properties"] + let route2Props = routes.[1].["properties"] + let route3Props = routes.[2].["properties"] + let route4Props = routes.[3].["properties"] + + Expect.equal + (string routeProps.["nextHopType"]) + "VirtualAppliance" + "route 1 should have a hop type of 'VirtualAppliance'" + + Expect.equal + (string routeProps.["addressPrefix"]) + "10.10.90.0/24" + "route 1 should have an address prefix of '10.10.90.0/24'" + + Expect.isNull route2Props.["nextHopIpAddress"] "route 2 should not have a next hop ip address" + Expect.isNull route3Props.["nextHopIpAddress"] "route 3 should not have a next hop ip address" + + Expect.equal + (string route2Props.["nextHopType"]) + "None" + "route 2 should have the default set to None for nextHopType" + + Expect.equal + (string route4Props.["nextHopIpAddress"]) + "10.2.31.2" + "route 4 should have the next hop ip address set to 10.2.31.2" + } test "Create private endpoint" { let myNet = vnet { diff --git a/src/Tests/Tests.fsproj b/src/Tests/Tests.fsproj index be7cb7ce1..e4b93ce88 100644 --- a/src/Tests/Tests.fsproj +++ b/src/Tests/Tests.fsproj @@ -72,7 +72,7 @@ - + diff --git a/src/Tests/VirtualMachine.fs b/src/Tests/VirtualMachine.fs index d76e18a31..7ae749334 100644 --- a/src/Tests/VirtualMachine.fs +++ b/src/Tests/VirtualMachine.fs @@ -57,6 +57,24 @@ let tests = (resource.DiagnosticsProfile.BootDiagnostics.Enabled.GetValueOrDefault false) "Boot Diagnostics should be enabled" } + + test "By default, VM does not include Priority" { + let template = + let myVm = + vm { + name "myvm" + username "me" + } + + arm { add_resource myVm } + + let jobj = Newtonsoft.Json.Linq.JObject.Parse(template.Template |> Writer.toJson) + + let vmProperties = + jobj.SelectToken("resources[?(@.name=='myvm')].properties") :?> Newtonsoft.Json.Linq.JObject + + Expect.isNull (vmProperties.Property "priority") "Priority should not be set by default" + } test "Can create a basic virtual machine with managed boot diagnostics" { let resource = let myVm = @@ -527,6 +545,51 @@ let tests = $"NIC should only depend on its public IP, not also the vnet: {nicDependsOn}" } + test "Link new VM to existing vnet in different resource group" { + let myVnet = + Arm.Network.virtualNetworks.resourceId ("myvnet", groupName = "other-group") + + let template = + let myVm = + vm { + name "myvm" + username "azureuser" + link_to_unmanaged_vnet myVnet + subnet_name "default" + } + + arm { add_resource myVm } + + let jobj = Newtonsoft.Json.Linq.JObject.Parse(template.Template |> Writer.toJson) + let vmResource = jobj.SelectToken("resources[?(@.name=='myvm')]") + let vmDependsOn = (vmResource.["dependsOn"] :?> Newtonsoft.Json.Linq.JArray) + Expect.hasLength vmDependsOn 1 "Incorrect number of VM dependencies" + + Expect.sequenceEqual + vmDependsOn + (Newtonsoft.Json.Linq.JArray [ "[resourceId('Microsoft.Network/networkInterfaces', 'myvm-nic')]" ]) + $"VM should only depend on its NIC, not also the vnet: {vmDependsOn}" + + let nicResource = jobj.SelectToken("resources[?(@.name=='myvm-nic')]") + let nicDependsOn = (nicResource.["dependsOn"] :?> Newtonsoft.Json.Linq.JArray) + Expect.hasLength nicDependsOn 1 "NIC should only have 1 dependency - the public IP" + + let nicSubnetId = + nicResource + .SelectToken("properties.ipConfigurations[0].properties.subnet.id") + .ToString() + + Expect.equal + nicSubnetId + "[resourceId('other-group', 'Microsoft.Network/virtualNetworks/subnets', 'myvnet', 'default')]" + "NIC subnet should repect resource group specified in VM VNet" + + Expect.sequenceEqual + nicDependsOn + (Newtonsoft.Json.Linq.JArray [ "[resourceId('Microsoft.Network/publicIPAddresses', 'myvm-ip')]" ]) + $"NIC should only depend on its public IP, not also the vnet: {nicDependsOn}" + } + test "Enables Azure AD SSH access on Linux virtual machine" { let template = let myVm = diff --git a/src/Tests/test-data/lots-of-resources.json b/src/Tests/test-data/lots-of-resources.json index e94613ded..6c988cb30 100644 --- a/src/Tests/test-data/lots-of-resources.json +++ b/src/Tests/test-data/lots-of-resources.json @@ -658,7 +658,6 @@ "adminUsername": "farmer-admin", "computerName": "farmervm" }, - "priority": "Regular", "storageProfile": { "dataDisks": [ { diff --git a/src/Tests/test-data/vm.json b/src/Tests/test-data/vm.json index 89387795f..d6d08747e 100644 --- a/src/Tests/test-data/vm.json +++ b/src/Tests/test-data/vm.json @@ -38,7 +38,6 @@ "adminUsername": "isaac", "computerName": "isaacsVM" }, - "priority": "Regular", "storageProfile": { "dataDisks": [ {