Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/Farmer/Arm/ConfigurationStore.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
[<AutoOpen>]
module Farmer.Arm.ConfigurationStore

open Farmer

let configurationStores =
ResourceType("Microsoft.AppConfiguration/configurationStores", "2024-05-01")

let keyValues =
ResourceType("Microsoft.AppConfiguration/configurationStores/keyValues", "2024-05-01")

type AzFeatureFlag = {
Name: string
Description: string
Label: string
State: bool
ConfigurationStoreId: ResourceId
} with

member this.ResourceName =
ResourceName
$"[format('{{0}}/{{1}}', '{this.ConfigurationStoreId.Name.Value}', format('.appconfig.featureflag~2F{{0}}${{1}}', '{this.Name}', '{this.Label}'))]"

interface IArmResource with
member this.ResourceId = keyValues.resourceId this.ResourceName

member this.JsonModel =
let enabled = this.State |> string |> _.ToLower()

let featureFlagValue =
$"""{{"id":"{this.Name}","description":"{this.Description}","enabled":{enabled}}}"""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is freeform text, so it will need JSON escaping. One quick way would be like:

[
  "id", box this.Name
  "description", this.Description
  "enabled", enabled
] |> dict |> System.Text.Json.JsonSerializer.Serialize


{|
keyValues.Create(this.ResourceName, dependsOn = [ this.ConfigurationStoreId ]) with
properties = {|
value = featureFlagValue
contentType = "application/vnd.microsoft.appconfig.ff+json;charset=utf-8"
|}
|}

type ConfigSku =
| Free
| Developer
| Standard
| Premium

member this.ArmValue =
match this with
| Free -> "free"
| Developer -> "developer"
| Standard -> "standard"
| Premium -> "premium"

type DataPlaneAuthenticationMode =
| Local
| Passthrough

member this.ArmValue =
match this with
| Local -> "Local"
| Passthrough -> "Pass-through"

type ConfigurationStore = {
Name: ResourceName
Location: Location
Sku: ConfigSku
DisableLocalAuth: bool
DataPlaneAuthenticationMode: DataPlaneAuthenticationMode
Tags: Map<string, string>
} with

interface IArmResource with
member this.ResourceId = configurationStores.resourceId this.Name

member this.JsonModel = {|
configurationStores.Create(this.Name, this.Location, tags = this.Tags) with
sku = {| name = this.Sku.ArmValue |}
properties = {|
disableLocalAuth = this.DisableLocalAuth
dataPlaneProxy = {|
authenticationMode = this.DataPlaneAuthenticationMode.ArmValue
|}
|}
|}
102 changes: 102 additions & 0 deletions src/Farmer/Builders/Builders.ConfigurationStore.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
[<AutoOpen>]
module Farmer.Builders.ConfigurationStore

open Farmer
open Farmer.Arm.ConfigurationStore

type FeatureFlagConfig = {
Name: string
Description: string
Label: string
State: bool
} with

member this.BuildResource(configurationStore: ResourceId) =
{
Name = this.Name
Description = this.Description
Label = this.Label
State = this.State
ConfigurationStoreId = configurationStore
}
:> IArmResource

type ConfigurationStoreConfig = {
Name: ResourceName
Location: Location
Sku: ConfigSku
DisableLocalAuth: bool
DataPlaneAuthenticationMode: DataPlaneAuthenticationMode
FeatureFlags: FeatureFlagConfig list
Tags: Map<string, string>
} with

interface IBuilder with
member this.ResourceId = configurationStores.resourceId this.Name

member this.BuildResources location = [
{
Name = this.Name
Location = location
Sku = this.Sku
DisableLocalAuth = this.DisableLocalAuth
DataPlaneAuthenticationMode = this.DataPlaneAuthenticationMode
Tags = this.Tags
}
yield!
this.FeatureFlags
|> List.map (fun ff -> ff.BuildResource(configurationStores.resourceId this.Name))
]

type ConfigurationStoreBuilder() =
member _.Yield _ = {
Name = ResourceName.Empty
Location = Location.NorthEurope

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be in the builder - it will get set on the resulting resource in the BuildResources call.

Sku = ConfigSku.Free
DisableLocalAuth = true
DataPlaneAuthenticationMode = DataPlaneAuthenticationMode.Passthrough
FeatureFlags = []
Tags = Map.empty
}

/// Sets the name of the configuration store.
[<CustomOperation "name">]
member _.Name(state: ConfigurationStoreConfig, name) = { state with Name = ResourceName name }

/// Sets the location of the configuration store.
[<CustomOperation "location">]
member _.Location(state: ConfigurationStoreConfig, location) = { state with Location = location }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can also remove this location operation since this is set automatically with by BuildResources.


/// Sets the SKU of the configuration store.
[<CustomOperation "sku">]
member _.Sku(state: ConfigurationStoreConfig, sku) = { state with Sku = sku }

/// Disables local authentication for the configuration store.
[<CustomOperation "disable_local_auth">]
member _.DisableLocalAuth(state: ConfigurationStoreConfig, disable: bool) = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this member is called "disable_local_auth", it doesn't need the boolean argument and should just set DisableLocalAuth to true when the operation is called.

state with
DisableLocalAuth = disable
}

/// Sets the data plane authentication mode for the configuration store.
[<CustomOperation "data_plane_authentication_mode">]
member _.DataPlaneAuthenticationMode(state: ConfigurationStoreConfig, mode) = {
state with
DataPlaneAuthenticationMode = mode
}

/// Adds a feature flag to the configuration store.
[<CustomOperation "feature_flags">]
member _.FeatureFlags(state: ConfigurationStoreConfig, featureFlags: FeatureFlagConfig list) = {
state with
FeatureFlags = featureFlags
}

interface ITaggable<ConfigurationStoreConfig> with
/// Adds a tag to this Configuration Store.
member _.Add state tags = {
state with
Tags = state.Tags |> Map.merge tags
}

let configurationStore = ConfigurationStoreBuilder()
2 changes: 2 additions & 0 deletions src/Farmer/Farmer.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@

<Compile Include="Writer.fs" />
<Compile Include="Deploy.fs" />
<Compile Include="Arm\ConfigurationStore.fs" />
<Compile Include="Arm\ActionGroup.fs" />
<Compile Include="Arm\AutomationAccount.fs" />
<Compile Include="Arm\AutoscaleSettings.fs" />
Expand Down Expand Up @@ -126,6 +127,7 @@
<Compile Include="Arm\B2cTenant.fs" />
<Compile Include="IdentityExtensions.fs" />
<Compile Include="Builders/Extensions.fs" />
<Compile Include="Builders\Builders.ConfigurationStore.fs" />
<Compile Include="Builders\Builders.ActionGroup.fs" />
<Compile Include="Builders\Builders.AutoscaleSettings.fs" />
<Compile Include="Builders/Builders.AlertsManagement.fs" />
Expand Down