Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow deployment slot settings #862

Draft
wants to merge 107 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
f1ae6f1
Add support for ip-restrictions for webApp/Functions.
viktorvan Dec 5, 2021
b78a545
allow slot_settings
nargiz Dec 9, 2021
f529e22
update slotSettingNames to be set
nargiz Dec 10, 2021
849db95
Add support for multiple custom domains
r30e Jan 21, 2022
106596d
Use domainName as certificate name
r30e Jan 21, 2022
de2b2e8
update docs + release notes
r30e Jan 21, 2022
edc6aae
Only turn on "auto" extension for Windows
isaacabraham Jan 23, 2022
48626d1
Release notes
isaacabraham Jan 23, 2022
8b9f7d9
Merge remote-tracking branch 'origin/master' into fix-linux-extension
isaacabraham Jan 23, 2022
de2fb64
Expanded description of custom_domain operator
r30e Jan 24, 2022
3a882b7
Merge pull request #867 from CompositionalIT/fix-linux-extension
isaacabraham Jan 25, 2022
7c8fcfc
Fix host name binding error when deploying multiple custom domains
Jan 28, 2022
6c136c2
Add support for custom docker port for web apps
stevebelskie Jan 30, 2022
aa9c093
Update web app documentation for docker_port
stevebelskie Jan 30, 2022
47f8b38
Fix typo in App Setting (WEBSITE_PORT -> WEBSITES_PORT)
stevebelskie Jan 30, 2022
8518bb5
Make connection_string and connections_strings available for azure fu…
amine-mejaouel Feb 6, 2022
87a72a1
Add connection string docs for Azure Functions
amine-mejaouel Feb 6, 2022
934eaaf
ZoneRedundant for service plan
nargiz Feb 11, 2022
f9de8d4
more test cases for service plan
nargiz Feb 11, 2022
5dab8e7
Tiny cleanup
isaacabraham Feb 11, 2022
63673cd
Merge pull request #870 from stevebelskie/master
isaacabraham Feb 11, 2022
db67331
Fix release notes
isaacabraham Feb 11, 2022
04d33a8
Merge branch 'master' of github.com:CompositionalIT/farmer into add-i…
ninjarobot Feb 13, 2022
41864e0
Support for CIDR network blocks on WebApp IP address restrictions
ninjarobot Feb 13, 2022
561d3b7
Appending webapp restriction rules to end of list
ninjarobot Feb 14, 2022
31ff0ca
Update RELEASE_NOTES.md
ninjarobot Feb 14, 2022
c3515dc
Clean up 1.6.26 release notes entries.
ninjarobot Feb 14, 2022
e5e4e70
Merge branch 'master' into azurefunction-connectionstrings
ninjarobot Feb 14, 2022
3d0f1eb
Moving to correct version in release notes
ninjarobot Feb 14, 2022
cf09dfd
Merge branch 'master' of github.com:CompositionalIT/farmer into add-i…
ninjarobot Feb 14, 2022
774e2d4
Merge branch 'master' into comItZoneRedundant
nargiz Feb 14, 2022
2168606
use FeatureFlag for ZoneRedundant
nargiz Feb 14, 2022
04bc033
update docs
nargiz Feb 14, 2022
e5943be
Merge pull request #835 from viktorvan/add-iprestrictions
ninjarobot Feb 15, 2022
672ca19
Merge branch 'master' into azurefunction-connectionstrings
ninjarobot Feb 16, 2022
f773c44
Merge pull request #874 from amine-mejaouel/azurefunction-connections…
ninjarobot Feb 16, 2022
d23098f
Update RELEASE_NOTES for version 1.6.27
ninjarobot Feb 16, 2022
7b0610b
1.6.27 release updates
ninjarobot Feb 16, 2022
a619428
Bundle LICENSE and README in nuget
ninjarobot Feb 16, 2022
97fec9a
Merged from master
r30e Feb 16, 2022
ec78c6a
Merge pull request #866 from codatio/multiple-custom-domains
ninjarobot Feb 16, 2022
868787b
Cleanup release notes for 1.6.27 release
ninjarobot Feb 16, 2022
07e5284
Re-applied changes following pull from remote farmer master.
michaelwade-c5 Feb 2, 2022
8403226
Removed un-intended change
michaelwade-c5 Feb 17, 2022
c676c74
Removed un-intended change 2
michaelwade-c5 Feb 17, 2022
1647db4
Merge branch 'master' into comItZoneRedundant
nargiz Feb 21, 2022
5a8c297
Merge pull request #876 from nargiz/comItZoneRedundant
ninjarobot Mar 1, 2022
6e80b44
1.6.28 release
ninjarobot Mar 1, 2022
c554b65
Allow adding an IP with CIDR as a string to a web app
theprash Mar 4, 2022
696806e
Use --only-show-errors - fixes #884
forki Mar 7, 2022
9afadfb
fix core and memory
tforkmann Mar 7, 2022
bbb7d87
add hint to turn on ressource provider
tforkmann Mar 7, 2022
8c8b484
Merge pull request #885 from forki/master
isaacabraham Mar 7, 2022
2de2c8b
Merge pull request #886 from tforkmann/fix-containerapp-script
isaacabraham Mar 7, 2022
dcd0910
Merge pull request #887 from tforkmann/hint-resource-provider
isaacabraham Mar 7, 2022
004ef55
Merge pull request #880 from ninjarobot/nuget-updates
isaacabraham Mar 7, 2022
0ae6dea
1.6.29 release
ninjarobot Mar 7, 2022
a06cae7
Added support for WebApp vnet integration
r30e Mar 9, 2022
062fb5c
Added tests
r30e Mar 9, 2022
b60cc2a
docs
r30e Mar 9, 2022
ccc6404
merge
r30e Mar 9, 2022
2099a5d
cleanup
r30e Mar 9, 2022
1aa19ef
Merge branch 'master' into ip-cidr-as-string
theprash Mar 11, 2022
3e2592b
IP with CIDR: Add test and release notes
theprash Mar 11, 2022
5cb9a58
Merge pull request #883 from CompositionalIT/ip-cidr-as-string
isaacabraham Mar 11, 2022
3bd6686
App Insights now supports Log Analytics
isaacabraham Mar 12, 2022
4754169
add VM Priority and Spot Instance
bigjonroberts Mar 12, 2022
8d2053b
update tests for vm prioirity
bigjonroberts Mar 12, 2022
6fea7c3
add documentation for vm priority and spot instance
bigjonroberts Mar 12, 2022
de90ac8
add sample for vm spot instance
bigjonroberts Mar 12, 2022
c45f736
Update release notes to include VM Priority and Spot Instance
bigjonroberts Mar 12, 2022
1363c91
update release notes to 1.6.31 correctly
bigjonroberts Mar 12, 2022
540a71e
cleanup comments
bigjonroberts Mar 12, 2022
019a1ee
Add docs & samples.
isaacabraham Mar 13, 2022
522b3cf
Small rename
isaacabraham Mar 13, 2022
9dcf395
Merge remote-tracking branch 'origin/master' into feature/871-connect…
michaelwade-c5 Mar 14, 2022
42c2da0
Merge pull request #872 from mike-wade/feature/871-connection-string-…
isaacabraham Mar 14, 2022
78233b3
Merge branch 'master' into vm-spot-instance
bigjonroberts Mar 14, 2022
a9f32a4
Fix test
isaacabraham Mar 14, 2022
6578a0f
rename route_via_vnet -> vnet
r30e Mar 15, 2022
6407710
Added validation
r30e Mar 15, 2022
9fce32a
merged from master
r30e Mar 15, 2022
f9d8cd8
Updated release notes
r30e Mar 15, 2022
77c2b5b
Update release notes and simplify tests.
isaacabraham Mar 15, 2022
68a0fc1
Merge remote-tracking branch 'origin/master' into ai-workspaces
isaacabraham Mar 15, 2022
7e281ce
Merge pull request #895 from CompositionalIT/ai-workspaces
isaacabraham Mar 15, 2022
d8cf05e
Merge branch 'master' into vm-spot-instance
bigjonroberts Mar 16, 2022
9058d23
only allow one spot_instance or prioirity setting in VM
bigjonroberts Mar 17, 2022
cef4e04
correct some text on tests
bigjonroberts Mar 17, 2022
c9bc1c8
Add docs about what spot_instance actually does.
bigjonroberts Mar 17, 2022
c680a99
Update release notes changes to match tag on PR
bigjonroberts Mar 17, 2022
19a03d0
Merge pull request #894 from bigjonroberts/vm-spot-instance
ninjarobot Mar 17, 2022
bcdf853
1.6.30 release
ninjarobot Mar 17, 2022
7917662
update to resemble to vm builder instead of AKS builder
r30e Mar 18, 2022
65b614c
merge
r30e Mar 18, 2022
b012a44
- update docs
r30e Mar 18, 2022
fd24ceb
Add validation for functions
r30e Mar 18, 2022
3ac3498
AzureFirewall supports AvailabilityZones
Mar 22, 2022
e8ae29e
Release notes
Mar 22, 2022
5f3ada6
Fix test failure due to increased validation
r30e Mar 22, 2022
5e34581
Adds JSON selection test for zonal AzFirewall
ninjarobot Mar 22, 2022
bf094e5
Merge pull request #888 from codatio/vnet-integration
ninjarobot Mar 22, 2022
5737fc1
fix resource id for resource group config
Mar 21, 2022
6a4233d
Merge branch 'master' into ersoler/multiaz-firewall
ericsoler1 Mar 22, 2022
1d4eeb6
Merge pull request #900 from ericsoler1/ersoler/multiaz-firewall
ninjarobot Mar 22, 2022
a2eed20
merge with latest
nargiz Mar 23, 2022
a2bce2c
improvements for slot settings
nargiz Mar 24, 2022
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
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
Release Notes
=============
## vNext
* WebApp/Functions: Support for deployment slot settings with `slot_setting` and `slot_settings`

## 1.6.26
* WebApp/Functions: Fix .NET on Linux deployments

Expand Down
2 changes: 2 additions & 0 deletions docs/content/api-overview/resources/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ The Functions builder is used to create Azure Functions accounts. It abstracts t
| publish_as | Specifies whether to publish function as code or as a docker container. |
| add_slot | Adds a deployment slot to the app |
| add_slots | Adds multiple deployment slots to the app |
| slot_setting | Sets a deployment slot setting of the function in the form "key" "value". |
Copy link
Member

Choose a reason for hiding this comment

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

I wonder whether it's possible to piggy back on the existing setting keywords? Also, there's also the secret_setting keyword for adding secure settings - this should probably be migrated as well if we keep with separate keywords.

Copy link
Collaborator

Choose a reason for hiding this comment

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

How does this really differ from including config when adding a slot with add_slot and using the SlotBuilder with addSlot? I see this PR adds slotconfignames when you add these - is the purpose of this just for sticky config?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To make settings sticky, their names should be specified in webapp(sites) child resource, slotconfignames. We usually add settings in webapp, so the idea was to have slot_setting which adds the setting and creates child resource slotconfignames.

The alternative solution was something similar to this:

let myWebApp = webApp {
    name "test"
    add_slot deploymentSlot

    settings ["my_setting", "test value"
                  "my_slot_setting", "sticky value"]

    slot_setting_names ["my_slot_setting" ]
}

Where slot_setting_names was only marking the settings as sticky by adding slotconfignames, But we thought the current solution with slot_setting will make things clearer.

| slot_settings | Sets a list of deployment slot setting of the function as tuples in the form of ("key", "value"). |
| health_check_path | Sets the path to your functions health check endpoint, which Azure load balancers will ping to determine which instances are healthy.|

#### Post-deployment Builder Keywords
Expand Down
2 changes: 2 additions & 0 deletions docs/content/api-overview/resources/web-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ The Web App builder is used to create Azure App Service accounts. It abstracts t
| Web App | add_private_endpoints | Adds private endpoints for this Webapp to the given subnets |
| Web App | add_slot | Adds a deployment slot to the app |
| Web App | add_slots | Adds multiple deployment slots to the app |
| Web App | slot_setting | Sets a deployment slot setting of the web app in the form "key" "value". |
| Web App | slot_settings | Sets a list of deployment slot setting of the web app as tuples in the form of ("key", "value"). |
| Web App | health_check_path | Sets the path to your functions health check endpoint, which Azure load balancers will ping to determine which instances are healthy.|
| Web App | custom_domain | Adds custom domain to the app, containing an app service managed certificate |
| Service Plan | service_plan_name | Sets the name of the service plan. If not set, uses the name of the web app postfixed with "-plan". |
Expand Down
13 changes: 12 additions & 1 deletion src/Farmer/Arm/Web.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ open System

let serverFarms = ResourceType ("Microsoft.Web/serverfarms", "2018-02-01")
let sites = ResourceType ("Microsoft.Web/sites", "2020-06-01")
let config = ResourceType ("Microsoft.Web/sites/config", "2016-08-01")
let config = ResourceType ("Microsoft.Web/sites/config", "2020-06-01")
let sourceControls = ResourceType ("Microsoft.Web/sites/sourcecontrols", "2019-08-01")
let staticSites = ResourceType ("Microsoft.Web/staticSites", "2019-12-01-preview")
let siteExtensions = ResourceType ("Microsoft.Web/sites/siteextensions", "2020-06-01")
Expand Down Expand Up @@ -400,6 +400,17 @@ type Certificate =
canonicalName = this.DomainName |}
|}

type SlotConfigName =
{ SiteName : ResourceName
SlotSettingNames: string Set }
interface IArmResource with
member this.ResourceId = config.resourceId(this.SiteName/"slotconfignames")
member this.JsonModel =
{| config.Create(this.SiteName/"slotconfignames", dependsOn = [ sites.resourceId this.SiteName]) with
kind = "string"
properties = {| appSettingNames = this.SlotSettingNames |}
|} :> _

[<AutoOpen>]
module SiteExtensions =
type SiteExtension =
Expand Down
9 changes: 8 additions & 1 deletion src/Farmer/Builders/Builders.Functions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,12 @@ type FunctionsConfig =
{ site with AppSettings = None; ConnectionStrings = None }
for (_, slot) in this.CommonWebConfig.Slots |> Map.toSeq do
slot.ToSite site

if this.CommonWebConfig.SlotSettingNames <> Set.empty then
{
Copy link
Member

Choose a reason for hiding this comment

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

Formatting - I actually do prefer this style but the rest of Farmer uses the more common record creation style, could you follow that? Also ; unnecessary and can be removed.

SiteName = this.Name.ResourceName;
SlotSettingNames = this.CommonWebConfig.SlotSettingNames;
}
]

type FunctionsBuilder() =
Expand All @@ -343,7 +349,8 @@ type FunctionsBuilder() =
Slots = Map.empty
WorkerProcess = None
ZipDeployPath = None
HealthCheckPath = None }
HealthCheckPath = None
SlotSettingNames = Set.empty }
StorageAccount = derived (fun config ->
let storage = config.Name.ResourceName.Map (sprintf "%sstorage") |> sanitiseStorage |> ResourceName
storageAccounts.resourceId storage)
Expand Down
25 changes: 23 additions & 2 deletions src/Farmer/Builders/Builders.WebApp.fs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ type CommonWebConfig =
Slots : Map<string,SlotConfig>
WorkerProcess : Bitness option
ZipDeployPath : (string*ZipDeploy.ZipDeploySlot) option
HealthCheckPath: string option }
HealthCheckPath: string option
SlotSettingNames: string Set }

type WebAppConfig =
{ CommonWebConfig: CommonWebConfig
Expand Down Expand Up @@ -540,6 +541,12 @@ type WebAppConfig =
DomainName = customDomain
SslState = SslDisabled }
| NoDomain -> ()

if this.CommonWebConfig.SlotSettingNames <> Set.empty then
{
Copy link
Member

Choose a reason for hiding this comment

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

As above re: record style.

SiteName = this.Name.ResourceName;
SlotSettingNames = this.CommonWebConfig.SlotSettingNames;
}

yield! (PrivateEndpoint.create location this.ResourceId ["sites"] this.PrivateEndpoints)
]
Expand All @@ -562,7 +569,8 @@ type WebAppBuilder() =
Slots = Map.empty
WorkerProcess = None
ZipDeployPath = None
HealthCheckPath = None }
HealthCheckPath = None
SlotSettingNames = Set.empty }
Sku = Sku.F1
WorkerSize = Small
WorkerCount = 1
Expand Down Expand Up @@ -888,3 +896,16 @@ module Extensions =
[<CustomOperation "health_check_path">]
/// Specifies the path Azure load balancers will ping to check for unhealthy instances.
member this.HealthCheckPath(state:'T, healthCheckPath:string) = this.Map state (fun x -> {x with HealthCheckPath = Some(healthCheckPath)})

/// Adds slot settings
[<CustomOperation "slot_setting">]
member this.AddSlotSetting (state:'T, key, value) =
let current = this.Get state
{ current with Settings = current.Settings.Add(key, LiteralSetting value); SlotSettingNames =current.SlotSettingNames.Add(key) }
Copy link
Member

Choose a reason for hiding this comment

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

Newline for each updated field please.

Copy link
Member

Choose a reason for hiding this comment

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

And space before and after =

|> this.Wrap state
[<CustomOperation "slot_settings">]
Copy link
Collaborator

Choose a reason for hiding this comment

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

The convention is to use add_ when adding items to the config, so this should be add_slot_settings.

Copy link
Contributor

@r30e r30e Mar 16, 2022

Choose a reason for hiding this comment

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

Given that setting[s] doesn't follow that convention, would it be best if operator aligned with setting[s] or with other operators?

member this.AddSlotSettings(state:'T, settings: (string*string) list) =
Copy link
Collaborator

Choose a reason for hiding this comment

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

If this is needed independently of adding slots, it at least should support the same capabilities as the settings in slots by using the Setting type instead of just strings. Then it can be a literal or an ARM expression.

let current = this.Get state
settings
|> List.fold (fun (state:CommonWebConfig) (key, value: string) -> { state with Settings = state.Settings.Add(key, LiteralSetting value); SlotSettingNames = state.SlotSettingNames.Add(key) }) current
|> this.Wrap state
59 changes: 59 additions & 0 deletions src/Tests/Functions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,63 @@ let tests = testList "Functions tests" [
|> Seq.map (fun x-> x.ToObject<{|name:string;value:string|}> ())
Expect.contains appSettings {|name="APPINSIGHTS_INSTRUMENTATIONKEY"; value="[reference(resourceId('shared-group', 'Microsoft.Insights/components', 'theName'), '2014-04-01').InstrumentationKey]"|} "Invalid value for APPINSIGHTS_INSTRUMENTATIONKEY"
}

test "Supports slot settings" {
let functionsApp = functions { name "test"; slot_settings [ "sticky_config", "sticky_config_value"; "another_sticky_config", "another_sticky_config_value" ]}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could any of these tests be enhanced to make it a practical example that would show usage along with the existing DSL to add_slots?


let scn = functionsApp |> getResources |> getResource<Web.SlotConfigName> |> List.head
let ws = functionsApp |> getResources |> getResource<Web.Site> |> List.head

let template = arm{ add_resource functionsApp}
let jobj = template.Template |> Writer.toJson |> Newtonsoft.Json.Linq.JObject.Parse

let appSettingNames =
jobj.SelectTokens $"$..resources[?(@name=='{functionsApp.Name.ResourceName.Value}/slotconfignames')].properties.appSettingNames[*]"
|> Seq.map (fun x -> x.ToString())

let dependencies =
jobj.SelectTokens $"$..resources[?(@name=='{functionsApp.Name.ResourceName.Value}/slotconfignames')].dependsOn[*]"
|> Seq.map (fun x -> x.ToString())

let expectedSettings = Map [
"sticky_config", LiteralSetting "sticky_config_value"
"another_sticky_config", LiteralSetting "another_sticky_config_value" ]

let settings = Expect.wantSome ws.AppSettings "AppSettings should be set"
Expect.containsAll settings expectedSettings "App settings should contain the slot settings"
Expect.containsAll scn.SlotSettingNames ["sticky_config"; "another_sticky_config"] "Slot config names should be set"
Expect.equal scn.SiteName (ResourceName "test") "Parent name should be set"
Expect.containsAll appSettingNames [ "sticky_config"; "another_sticky_config"] "Slot config names should be present in template"
Expect.containsAll dependencies [ $"[resourceId('Microsoft.Web/sites', '{functionsApp.Name.ResourceName.Value}')]"] "Slot config names resource should depend on web site"

}

test "Supports slot setting" {
let functionsApp = functions { name "test"; slot_setting "sticky_config" "sticky_config_value" }

let scn = functionsApp |> getResources |> getResource<Web.SlotConfigName> |> List.head
let ws = functionsApp |> getResources |> getResource<Web.Site> |> List.head

let template = arm{ add_resource functionsApp}
let jobj = template.Template |> Writer.toJson |> Newtonsoft.Json.Linq.JObject.Parse

let appSettingNames =
jobj.SelectTokens $"$..resources[?(@name=='{functionsApp.Name.ResourceName.Value}/slotconfignames')].properties.appSettingNames[*]"
|> Seq.map (fun x -> x.ToString())

let dependencies =
jobj.SelectTokens $"$..resources[?(@name=='{functionsApp.Name.ResourceName.Value}/slotconfignames')].dependsOn[*]"
|> Seq.map (fun x -> x.ToString())

let expectedSettings = Map [
"sticky_config", LiteralSetting "sticky_config_value" ]

let settings = Expect.wantSome ws.AppSettings "AppSettings should be set"
Expect.containsAll settings expectedSettings "App settings should contain the slot setting"
Expect.containsAll scn.SlotSettingNames ["sticky_config"] "Slot config name should be set"
Expect.equal scn.SiteName (ResourceName "test") "Parent name should be set"
Expect.containsAll appSettingNames [ "sticky_config" ] "Slot config name should be present in template"
Expect.containsAll dependencies [ $"[resourceId('Microsoft.Web/sites', '{functionsApp.Name.ResourceName.Value}')]"] "Slot config names resource should depend on web site"

}
]
59 changes: 59 additions & 0 deletions src/Tests/WebApp.fs
Original file line number Diff line number Diff line change
Expand Up @@ -710,4 +710,63 @@ let tests = testList "Web App Tests" [

Expect.equal hostnameBinding.Length 0 $"There should not be a hostname binding as a result of choosing the 'NoDomain' option"
}

test "Supports slot settings" {
let webApp = webApp { name "test"; slot_settings [ "sticky_config", "sticky_config_value"; "another_sticky_config", "another_sticky_config_value" ]}

let scn = webApp |> getResources |> getResource<Web.SlotConfigName> |> List.head
let ws = webApp |> getResources |> getResource<Web.Site> |> List.head

let template = arm{ add_resource webApp}
let jobj = template.Template |> Writer.toJson |> Newtonsoft.Json.Linq.JObject.Parse

let appSettingNames =
jobj.SelectTokens $"$..resources[?(@name=='{webApp.Name.ResourceName.Value}/slotconfignames')].properties.appSettingNames[*]"
|> Seq.map (fun x -> x.ToString())

let dependencies =
jobj.SelectTokens $"$..resources[?(@name=='{webApp.Name.ResourceName.Value}/slotconfignames')].dependsOn[*]"
|> Seq.map (fun x -> x.ToString())

let expectedSettings = Map [
"sticky_config", LiteralSetting "sticky_config_value"
"another_sticky_config", LiteralSetting "another_sticky_config_value" ]

let settings = Expect.wantSome ws.AppSettings "AppSettings should be set"
Expect.containsAll settings expectedSettings "App settings should contain the slot settings"
Expect.containsAll scn.SlotSettingNames ["sticky_config"; "another_sticky_config"] "Slot config names should be set"
Expect.equal scn.SiteName (ResourceName "test") "Parent name should be set"
Expect.containsAll appSettingNames [ "sticky_config"; "another_sticky_config"] "Slot config names should be present in template"
Expect.containsAll dependencies [ $"[resourceId('Microsoft.Web/sites', '{webApp.Name.ResourceName.Value}')]"] "Slot config names resource should depend on web site"

}

test "Supports slot setting" {
let webApp = webApp { name "test"; slot_setting "sticky_config" "sticky_config_value" }

let scn = webApp |> getResources |> getResource<Web.SlotConfigName> |> List.head
let ws = webApp |> getResources |> getResource<Web.Site> |> List.head

let template = arm{ add_resource webApp}
let jobj = template.Template |> Writer.toJson |> Newtonsoft.Json.Linq.JObject.Parse

let appSettingNames =
jobj.SelectTokens $"$..resources[?(@name=='{webApp.Name.ResourceName.Value}/slotconfignames')].properties.appSettingNames[*]"
|> Seq.map (fun x -> x.ToString())

let dependencies =
jobj.SelectTokens $"$..resources[?(@name=='{webApp.Name.ResourceName.Value}/slotconfignames')].dependsOn[*]"
|> Seq.map (fun x -> x.ToString())

let expectedSettings = Map [
"sticky_config", LiteralSetting "sticky_config_value" ]

let settings = Expect.wantSome ws.AppSettings "AppSettings should be set"
Expect.containsAll settings expectedSettings "App settings should contain the slot setting"
Expect.containsAll scn.SlotSettingNames ["sticky_config"] "Slot config name should be set"
Expect.equal scn.SiteName (ResourceName "test") "Parent name should be set"
Expect.containsAll appSettingNames [ "sticky_config" ] "Slot config name should be present in template"
Expect.containsAll dependencies [ $"[resourceId('Microsoft.Web/sites', '{webApp.Name.ResourceName.Value}')]"] "Slot config names resource should depend on web site"

}
]