Skip to content

Commit

Permalink
Merge pull request #1138 from ninjarobot/default-location-rg
Browse files Browse the repository at this point in the history
Deployments: Default to resource group location rather than West Europe.
  • Loading branch information
isaacabraham authored Oct 26, 2024
2 parents cc02ad0 + 4ff2d14 commit 6dd7b38
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 46 deletions.
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Release Notes
* Az: Update `ad` commands to work with latest (breaking) structure.
* PostgreSQL: Fix a number of issues around the introduction of Flexible Servers.

## 1.9.3
* Deployments: Default to resource group location rather than West Europe.

## 1.9.2
* Container Apps: Fix to container registry credential to not emit a secret for a managed identity.
* Container Groups: followup to #ff78f202dc - expand DNS config validation for profile-less vnet.
Expand Down
2 changes: 1 addition & 1 deletion src/Farmer/Builders/Builders.ResourceGroup.fs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ type DeploymentBuilder() =
Resources = List.empty
ParameterValues = List.empty
SubscriptionId = None
Location = Location.WestEurope
Location = Location.ResourceGroup
Mode = Incremental
Tags = Map.empty
}
Expand Down
3 changes: 3 additions & 0 deletions src/Farmer/Common.fs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ module LocationExtensions =
static member NorwayEast = Location "NorwayEast"
static member Global = Location "global"

static member ResourceGroup =
LocationExpression(ArmExpression.create "resourceGroup().location")

[<AutoOpen>]
module DataLocationExtensions =
type DataLocation with
Expand Down
39 changes: 23 additions & 16 deletions src/Farmer/Deploy.fs
Original file line number Diff line number Diff line change
Expand Up @@ -309,36 +309,43 @@ let private prepareForDeployment parameters resourceGroupName (deployment: IDepl
do! deployment |> validateParameters parameters

let! version = Az.checkVersion Az.MinimumVersion
printfn "Compatible version of Azure CLI %O detected" version
stdout.WriteLine $"Compatible version of Azure CLI {version} detected"

prepareDeploymentFolder ()

let! subscriptionDetails =
printf "Checking Azure CLI logged in status... "
stdout.Write "Checking Azure CLI logged in status... "

match Az.showAccount () with
| Ok response ->
printfn "you are already logged in, nothing to do."
stdout.WriteLine "you are already logged in, nothing to do."
Ok response
| Error _ ->
printfn "logging you in."
stdout.WriteLine "logging you in."
Az.login () |> Result.bind (fun _ -> Az.showAccount ())

let subscriptionDetails =
subscriptionDetails |> Serialization.ofJson<{| id: Guid; name: string |}>

printfn "Using subscription '%s' (%O)." subscriptionDetails.name subscriptionDetails.id
stdout.WriteLine $"Using subscription '%s{subscriptionDetails.name}' ({subscriptionDetails.id})."

let resourceGroups =
(resourceGroupName :: deployment.Deployment.RequiredResourceGroups)
|> List.distinct
// Filter out any resource groups that are an ARM expression calculated at deploy-time
|> List.filter (fun resGroupName -> not (resGroupName.StartsWith("[")))
|> List.mapi (fun i x -> i, x)
match deployment.Deployment.Location with
| Location _ ->
let resourceGroups =
(resourceGroupName :: deployment.Deployment.RequiredResourceGroups)
|> List.distinct
// Filter out any resource groups that are an ARM expression calculated at deploy-time
|> List.filter (fun resGroupName -> not (resGroupName.StartsWith("[")))
|> List.mapi (fun i x -> i, x)

for (i, rg) in resourceGroups do
printfn $"Creating resource group {rg} ({i + 1}/{resourceGroups.Length})..."
do! Az.createResourceGroup deployment.Deployment.Location.ArmValue deployment.Deployment.Tags rg
for (i, rg) in resourceGroups do
stdout.WriteLine $"Creating resource group {rg} ({i + 1}/{resourceGroups.Length})..."
do! Az.createResourceGroup deployment.Deployment.Location.ArmValue deployment.Deployment.Tags rg
| LocationExpression _ ->
stdout.WriteLine
"Deployment location is an ARM expression that cannot be evaluated by the CLI. Skipping resource group creation."

return () // Cannot evaluate an ARM expression in Az CLI.

return {|
DeploymentName = $"farmer-deploy-{generateDeployNumber ()}"
Expand Down Expand Up @@ -374,7 +381,7 @@ let tryWhatIf resourceGroupName parameters (deployment: IDeploymentSource) = res
let tryExecute resourceGroupName parameters (deployment: IDeploymentSource) = result {
let! deploymentParameters = deployment |> prepareForDeployment parameters resourceGroupName

printfn "Deploying ARM template (please be patient, this can take a while)..."
stdout.WriteLine "Deploying ARM template (please be patient, this can take a while)..."

let! response =
Az.deploy resourceGroupName deploymentParameters.DeploymentName deploymentParameters.TemplateFilename parameters
Expand All @@ -388,7 +395,7 @@ let tryExecute resourceGroupName parameters (deployment: IDeploymentSource) = re
|> Result.sequence
|> Result.ignore

printfn "All done, now parsing ARM response to get any outputs..."
stdout.WriteLine "All done, now parsing ARM response to get any outputs..."

let! response =
response
Expand Down
61 changes: 33 additions & 28 deletions src/Farmer/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,6 @@ module ResourceName =
| true, _ -> None
| false, _ -> Some Unparsed

type Location =
| Location of string

member this.ArmValue =
match this with
| Location location -> location.ToLower()

type DataLocation =
| DataLocation of string

member this.ArmValue =
match this with
| DataLocation dataLocation -> dataLocation

type ResourceType =
| ResourceType of path: string * version: string

Expand Down Expand Up @@ -134,20 +120,6 @@ module internal Patterns =
| t when t = expected -> Some(HasResourceType())
| _ -> None

/// An Azure ARM resource value which can be mapped into an ARM template.
type IArmResource =
/// The name of the resource, to uniquely identify against other resources in the template.
abstract member ResourceId: ResourceId
/// A raw object that is ready for serialization directly to JSON.
abstract member JsonModel: obj

/// Represents a high-level configuration that can create a set of ARM Resources.
type IBuilder =
/// Given a location and the currently-built resources, returns a set of resource actions.
abstract member BuildResources: Location -> IArmResource list
/// Provides the ResourceId that other resources should use when depending upon this builder.
abstract member ResourceId: ResourceId

/// Represents an expression used within an ARM template
type ArmExpression =
private
Expand Down Expand Up @@ -212,6 +184,39 @@ type ArmExpression =
static member string(value: ArmExpression) =
value.Value |> sprintf "string(%s)" |> ArmExpression.create

type Location =
| Location of string
| LocationExpression of ArmExpression

member this.ArmValue =
let v =
match this with
| Location location -> location.ToLower()
| LocationExpression expr -> expr.Eval()

v

type DataLocation =
| DataLocation of string

member this.ArmValue =
match this with
| DataLocation dataLocation -> dataLocation

/// An Azure ARM resource value which can be mapped into an ARM template.
type IArmResource =
/// The name of the resource, to uniquely identify against other resources in the template.
abstract member ResourceId: ResourceId
/// A raw object that is ready for serialization directly to JSON.
abstract member JsonModel: obj

/// Represents a high-level configuration that can create a set of ARM Resources.
type IBuilder =
/// Given a location and the currently-built resources, returns a set of resource actions.
abstract member BuildResources: Location -> IArmResource list
/// Provides the ResourceId that other resources should use when depending upon this builder.
abstract member ResourceId: ResourceId

type ResourceId with

member this.ArmExpression =
Expand Down
2 changes: 1 addition & 1 deletion src/Tests/LogAnalytics.fs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ let tests =

let workspace = asAzureResource config

Expect.equal workspace.Location "westeurope" "Incorrect Location"
Expect.equal workspace.Location "[resourceGroup().location]" "Incorrect Location"
Expect.equal workspace.Name "myFarmer" "Incorrect Name"
Expect.equal workspace.PublicNetworkAccessForIngestion "Enabled" "Incorrect IngestionSupport"
Expect.equal workspace.PublicNetworkAccessForQuery "Enabled" "QuerySupport"
Expand Down
20 changes: 20 additions & 0 deletions src/Tests/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,32 @@ module Types

open Expecto
open Farmer
open Farmer.Builders
open System
open Newtonsoft.Json.Linq

let tests =
testList "Type Tests" [
test "Creates deterministic GUID correctly" {
let actual = DeterministicGuid.create "hello"
Expect.equal (Guid.Parse "4fbe461c-3438-55c4-941e-d1c2013210c5") actual "Incorrect GUID"
}
test "Location.ResourceGroup emits correct ARM expression" {
Expect.equal
Location.ResourceGroup.ArmValue
"[resourceGroup().location]"
"Incorrect expression emitted for Location.ResourceGroup"
}
ftest "Default location for 'arm' builder uses resourceGroup location" {
let deployment =
let dummyResource = storageAccount { name "mystorageaccount74785" }
arm { add_resource dummyResource }

let jobj = deployment.Template |> Writer.toJson |> JToken.Parse

Expect.equal
(jobj.SelectToken "resources[?(@.name=='mystorageaccount74785')].location")
(JValue "[resourceGroup().location]")
"Default location on resource should be resource group."
}
]

0 comments on commit 6dd7b38

Please sign in to comment.