├── samples
├── .gitignore
├── SampleApp
│ ├── SampleApp.fsproj
│ └── Program.fs
└── scripts
│ ├── data-lake.fsx
│ ├── tutorials
│ ├── webapp-deploy.fsx
│ ├── keyvault-certs-app.fsx
│ ├── multiple-web-apps.fsx
│ ├── cosmos-backed-webapp.fsx
│ ├── custom-output.fsx
│ ├── serverless-etl.fsx
│ ├── webapp-keyvault.fsx
│ └── aci-fsharp.fsx
│ ├── webapp-appinsights.fsx
│ ├── appinsights.fsx
│ ├── search.fsx
│ ├── redis.fsx
│ ├── loganalytics.fsx
│ ├── functions.fsx
│ ├── iot.fsx
│ ├── vm.fsx
│ ├── appinsights-loganalytics.fsx
│ ├── databricks.fsx
│ ├── vm-spot-instance.fsx
│ ├── container-registry.fsx
│ ├── nested-resourcegroups.fsx
│ ├── deployment-script.fsx
│ ├── container-group.fsx
│ ├── bastion.fsx
│ ├── cosmos.fsx
│ ├── eventgrid.fsx
│ ├── postgresql.fsx
│ ├── eventhubs.fsx
│ ├── operations-management.fsx
│ ├── logicapp.fsx
│ ├── keyvault-keys.fsx
│ ├── cdn.fsx
│ ├── container-instance-gpu.fsx
│ ├── webapp-storage.fsx
│ ├── service-bus.fsx
│ ├── safe-template.fsx
│ ├── eventgrid-fn.fsx
│ ├── vnet-gateway.fsx
│ ├── diagnosticsetting.fsx
│ ├── storage.fsx
│ ├── sqlserver.fsx
│ ├── vnet.fsx
│ ├── vm-delete-option.fsx
│ ├── keyvault.fsx
│ ├── container-instance.fsx
│ ├── aks.fsx
│ ├── container-app.fsx
│ └── dns.fsx
├── Icon.jpg
├── Logo.png
├── docs
├── -f
│ └── images
│ │ ├── deploy.jpg
│ │ ├── logo.png
│ │ ├── arm-graph.jpg
│ │ ├── comparison.png
│ │ ├── farmer-flow.jpg
│ │ ├── farmer-favicon.png
│ │ └── quickstarts
│ │ ├── serverless-etl.png
│ │ ├── webapp-cosmos.png
│ │ └── multiple-web-apps.png
├── content
│ ├── images
│ │ ├── cit.png
│ │ ├── deploy.jpg
│ │ ├── logo.png
│ │ ├── arm-graph.jpg
│ │ ├── comparison.png
│ │ ├── farmer-flow.jpg
│ │ ├── farmer-favicon.png
│ │ └── tutorials
│ │ │ ├── webapp.png
│ │ │ ├── enterprise1.png
│ │ │ ├── enterprise2.png
│ │ │ ├── webapp-cosmos.png
│ │ │ ├── serverless-etl.png
│ │ │ ├── webapp-keyvault.png
│ │ │ ├── multiple-web-apps.png
│ │ │ ├── imperative-resource.png
│ │ │ └── webapp-keyvault-connection.png
│ ├── tutorials
│ │ ├── _index.md
│ │ ├── webapp-deploy.md
│ │ └── custom-output.md
│ ├── api-overview
│ │ ├── resources
│ │ │ ├── _index.md
│ │ │ ├── maps.md
│ │ │ ├── data-lake.md
│ │ │ ├── search.md
│ │ │ ├── bing-search.md
│ │ │ ├── communication-services.md
│ │ │ ├── cognitive-services.md
│ │ │ ├── virtual-hub.md
│ │ │ ├── container-registry.md
│ │ │ ├── redis.md
│ │ │ ├── virtual-wan.md
│ │ │ ├── signalr.md
│ │ │ ├── logic-apps.md
│ │ │ ├── databricks-workspace.md
│ │ │ ├── app-insights.md
│ │ │ ├── loganalytics.md
│ │ │ ├── iot-hub.md
│ │ │ ├── nat-gateway.md
│ │ │ ├── availability-tests.md
│ │ │ ├── private-endpoint.md
│ │ │ ├── dedicated-hosts.md
│ │ │ ├── static-web-app.md
│ │ │ ├── azure-firewall.md
│ │ │ └── operations-management.md
│ │ ├── basic-types.md
│ │ ├── outputs.md
│ │ └── template-generation.md
│ ├── quickstarts
│ │ ├── _index.md
│ │ ├── template.md
│ │ └── quickstart-1.md
│ ├── support
│ │ └── _index.md
│ ├── links
│ │ └── _index.md
│ ├── contributing
│ │ ├── _index.md
│ │ ├── outputs-and-expressions.md
│ │ ├── adding-resources
│ │ │ └── 5-unit-testing.md
│ │ └── create-pull-requests.md
│ ├── testimonials
│ │ └── _index.md
│ ├── about
│ │ └── _index.md
│ └── arm-vs-farmer
│ │ └── _index.md
├── archetypes
│ └── default.md
├── config.toml
└── README.md
├── global.json
├── .gitmodules
├── .config
└── dotnet-tools.json
├── .editorconfig
├── src
├── Tests
│ ├── test-data
│ │ ├── blank-logic-app.json
│ │ ├── virtual-wan.json
│ │ └── azure-firewall.json
│ ├── RoleAssignment.fs
│ ├── Maps.fs
│ ├── Types.fs
│ ├── CommunicationServices.fs
│ ├── EventHub.fs
│ ├── AvailabilityTests.fs
│ ├── StaticWebApp.fs
│ ├── Helpers.fs
│ ├── AzCli.fs
│ ├── BingSearch.fs
│ ├── B2cTenant.fs
│ ├── CognitiveServices.fs
│ ├── ResourceGroup.fs
│ ├── LogAnalytics.fs
│ ├── LogicApps.fs
│ └── IotHub.fs
└── Farmer
│ ├── Arm
│ ├── AVS.fs
│ ├── Webhook.fs
│ ├── AutomationAccount.fs
│ ├── LogicApps.fs
│ ├── CommunicationServices.fs
│ ├── Maps.fs
│ ├── CognitiveServices.fs
│ ├── DataLakeStore.fs
│ ├── ContainerRegistry.fs
│ ├── BingSearch.fs
│ ├── B2cTenant.fs
│ ├── OperationsManagement.fs
│ ├── Search.fs
│ ├── SignalR.fs
│ ├── VirtualWan.fs
│ ├── Cache.fs
│ ├── LogAnalytics.fs
│ ├── Disk.fs
│ └── Insights.fs
│ ├── Builders
│ ├── Builders.Helpers.fs
│ ├── Builders.Maps.fs
│ ├── Builders.DataLake.fs
│ ├── Builders.LogicApps.fs
│ ├── Builders.BingSearch.fs
│ ├── Builders.CognitiveServices.fs
│ └── Builders.NatGateway.fs
│ ├── Aliases.fs
│ └── Writer.fs
├── readme.md
├── pull_request_template.md
├── LICENSE
├── .github
└── workflows
│ └── gh-pages.yml
├── MAINTAINERS.md
└── CONTRIBUTING.md
/samples/.gitignore:
--------------------------------------------------------------------------------
1 | farmer-deploy.json
--------------------------------------------------------------------------------
/Icon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/Icon.jpg
--------------------------------------------------------------------------------
/Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/Logo.png
--------------------------------------------------------------------------------
/docs/-f/images/deploy.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/-f/images/deploy.jpg
--------------------------------------------------------------------------------
/docs/-f/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/-f/images/logo.png
--------------------------------------------------------------------------------
/docs/content/images/cit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/cit.png
--------------------------------------------------------------------------------
/docs/-f/images/arm-graph.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/-f/images/arm-graph.jpg
--------------------------------------------------------------------------------
/docs/-f/images/comparison.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/-f/images/comparison.png
--------------------------------------------------------------------------------
/docs/-f/images/farmer-flow.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/-f/images/farmer-flow.jpg
--------------------------------------------------------------------------------
/docs/content/images/deploy.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/deploy.jpg
--------------------------------------------------------------------------------
/docs/content/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/logo.png
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "8.0.206",
4 | "rollForward": "latestMinor"
5 | }
6 | }
--------------------------------------------------------------------------------
/docs/-f/images/farmer-favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/-f/images/farmer-favicon.png
--------------------------------------------------------------------------------
/docs/content/images/arm-graph.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/arm-graph.jpg
--------------------------------------------------------------------------------
/docs/content/images/comparison.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/comparison.png
--------------------------------------------------------------------------------
/docs/content/images/farmer-flow.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/farmer-flow.jpg
--------------------------------------------------------------------------------
/docs/archetypes/default.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "{{ replace .Name "-" " " | title }}"
3 | date: {{ .Date }}
4 | draft: true
5 | ---
6 |
7 |
--------------------------------------------------------------------------------
/docs/content/images/farmer-favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/farmer-favicon.png
--------------------------------------------------------------------------------
/docs/content/images/tutorials/webapp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/tutorials/webapp.png
--------------------------------------------------------------------------------
/docs/-f/images/quickstarts/serverless-etl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/-f/images/quickstarts/serverless-etl.png
--------------------------------------------------------------------------------
/docs/-f/images/quickstarts/webapp-cosmos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/-f/images/quickstarts/webapp-cosmos.png
--------------------------------------------------------------------------------
/docs/content/images/tutorials/enterprise1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/tutorials/enterprise1.png
--------------------------------------------------------------------------------
/docs/content/images/tutorials/enterprise2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/tutorials/enterprise2.png
--------------------------------------------------------------------------------
/docs/content/images/tutorials/webapp-cosmos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/tutorials/webapp-cosmos.png
--------------------------------------------------------------------------------
/docs/-f/images/quickstarts/multiple-web-apps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/-f/images/quickstarts/multiple-web-apps.png
--------------------------------------------------------------------------------
/docs/content/images/tutorials/serverless-etl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/tutorials/serverless-etl.png
--------------------------------------------------------------------------------
/docs/content/images/tutorials/webapp-keyvault.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/tutorials/webapp-keyvault.png
--------------------------------------------------------------------------------
/docs/content/images/tutorials/multiple-web-apps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/tutorials/multiple-web-apps.png
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "docs/themes/hugo-theme-learn"]
2 | path = docs/themes/hugo-theme-learn
3 | url = https://github.com/compositionalit/hugo-theme-learn.git
4 |
--------------------------------------------------------------------------------
/docs/content/images/tutorials/imperative-resource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/tutorials/imperative-resource.png
--------------------------------------------------------------------------------
/docs/content/images/tutorials/webapp-keyvault-connection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompositionalIT/farmer/HEAD/docs/content/images/tutorials/webapp-keyvault-connection.png
--------------------------------------------------------------------------------
/docs/content/tutorials/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Tutorials"
3 | date: 2020-10-24
4 | weight: 3
5 | ---
6 |
7 | This section contains tutorials for specific use-cases. Use these to help get ideas for how to model your Farmer resources!
--------------------------------------------------------------------------------
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "fantomas": {
6 | "version": "7.0.3",
7 | "commands": [
8 | "fantomas"
9 | ],
10 | "rollForward": false
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = false
9 |
10 | [*.{fs,fsx}]
11 | fsharp_multiline_bracket_style = stroustrup
12 | fsharp_newline_before_multiline_computation_expression = false
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Resources"
3 | date: 2020-02-04T22:37:39+01:00
4 | weight: 10
5 | chapter: true
6 | ---
7 |
8 | # Resources
9 |
10 | Farmer currently has support for the most popular resources in Azure, such as Storage, App Service and Functions. You can find out more about each resource in detail in the menu.
--------------------------------------------------------------------------------
/docs/content/quickstarts/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Quickstarts"
3 | date: 2020-02-04T00:41:03+01:00
4 | weight: 2
5 | ---
6 |
7 | This section contains quickstarts to performing common tasks.
8 |
9 | * [Creating your first Farmer template](quickstart-1)
10 | * [Creating multiple resources](quickstart-2)
11 | * [Deploying to Azure](quickstart-3)
12 | * [The Farmer .NET Template](template)
--------------------------------------------------------------------------------
/src/Tests/test-data/blank-logic-app.json:
--------------------------------------------------------------------------------
1 | {
2 | "definition": {
3 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
4 | "actions": {},
5 | "contentVersion": "1.0.0.0",
6 | "outputs": {},
7 | "parameters": {},
8 | "triggers": {}
9 | },
10 | "parameters": {}
11 | }
--------------------------------------------------------------------------------
/samples/SampleApp/SampleApp.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/samples/scripts/data-lake.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let myLake = dataLake {
7 | name "isaacsLake"
8 | enable_encryption
9 | sku DataLake.Commitment_10TB
10 | }
11 |
12 | let deployment = arm {
13 | location Location.NorthEurope
14 | add_resource myLake
15 | }
16 |
17 | deployment |> Deploy.execute "my-resource-group-name" Deploy.NoParameters
--------------------------------------------------------------------------------
/samples/scripts/tutorials/webapp-deploy.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let myWebApp = webApp {
7 | name ""
8 | zip_deploy @""
9 | }
10 |
11 | let template = arm {
12 | location Location.NorthEurope
13 | add_resource myWebApp
14 | }
15 |
16 | template |> Deploy.execute "mywebapp" Deploy.NoParameters
--------------------------------------------------------------------------------
/samples/scripts/webapp-appinsights.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let template =
7 | let myWebApp = webApp {
8 | name "mysuperwebapp"
9 | sku WebApp.Sku.F1
10 | }
11 |
12 | arm {
13 | location Location.NorthEurope
14 | add_resource myWebApp
15 | }
16 |
17 | template |> Deploy.execute "my-resource-group-name" Deploy.NoParameters
--------------------------------------------------------------------------------
/src/Farmer/Arm/AVS.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.AVS
3 |
4 | open Farmer
5 | let privateClouds = ResourceType("Microsoft.AVS/privateClouds", "2021-12-01")
6 |
7 | let privateCloudsScriptPackages =
8 | ResourceType("Microsoft.AVS/privateClouds/scriptPackages", "2021-12-01")
9 |
10 | let privateCloudsScriptCmdlets =
11 | ResourceType("Microsoft.AVS/privateClouds/scriptPackages/scriptCmdlets", "2021-12-01")
--------------------------------------------------------------------------------
/samples/scripts/appinsights.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let myAppInsights = appInsights { name "isaacsAi" }
7 |
8 | let myFunctions = functions {
9 | name "mysuperwebapp"
10 | link_to_app_insights myAppInsights.Name
11 | }
12 |
13 | let template = arm {
14 | location Location.NorthEurope
15 | add_resource myAppInsights
16 | add_resource myFunctions
17 | }
18 |
19 | template |> Deploy.execute "deleteme" Deploy.NoParameters
--------------------------------------------------------------------------------
/samples/scripts/search.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.Search
6 |
7 | let mySearch = search {
8 | name "isaacsSearch"
9 | sku Basic
10 | }
11 |
12 | let deployment = arm {
13 | location Location.NorthEurope
14 | add_resource mySearch
15 | output "search-admin-key" mySearch.AdminKey
16 | output "search-query-key" mySearch.QueryKey
17 | }
18 |
19 | deployment |> Deploy.execute "my-resource-group-name" Deploy.NoParameters
--------------------------------------------------------------------------------
/samples/SampleApp/Program.fs:
--------------------------------------------------------------------------------
1 | open Farmer
2 | open Farmer.Builders
3 |
4 | //TODO: Create resources here!
5 |
6 | let deployment = arm {
7 | location Location.NorthEurope
8 |
9 | //TODO: Assign resources here using the add_resource keyword
10 | }
11 |
12 | // Generate the ARM template here...
13 | deployment |> Writer.quickWrite @"generated-template"
14 |
15 | // Or deploy it directly to Azure here... (required Azure CLI installed!)
16 | // deployment
17 | // |> Deploy.execute "my-resource-group" Deploy.NoParameters
--------------------------------------------------------------------------------
/samples/scripts/redis.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let myCache = redis {
7 | name "myredis"
8 | sku Redis.Standard
9 | capacity 0
10 | enable_non_ssl_port
11 | setting "maxclients" 256
12 | setting "maxmemory-reserved" 2
13 | setting "maxfragmentationmemory-reserved" 12
14 | setting "maxmemory-delta" 2
15 | }
16 |
17 | let template = arm {
18 | location Location.NorthEurope
19 | add_resource myCache
20 | }
21 |
22 | template |> Writer.quickWrite "my-resource-group-name"
--------------------------------------------------------------------------------
/samples/scripts/loganalytics.fsx:
--------------------------------------------------------------------------------
1 | #r "./libs/Newtonsoft.Json.dll"
2 | #r "../../src/Farmer/bin/Debug/netstandard2.0/Farmer.dll"
3 |
4 | open Farmer
5 | open Farmer.Builders
6 |
7 | let myAnalytics = logAnalytics {
8 | name "isaacla"
9 | retention_period 50
10 | enable_ingestion
11 | enable_query
12 | daily_cap 5
13 | }
14 |
15 | let deployment = arm {
16 | location Location.WestEurope
17 | add_resource myAnalytics
18 | }
19 |
20 | deployment
21 | |> Deploy.execute "test-resource-group" Deploy.NoParameters
22 | |> printfn "%A"
--------------------------------------------------------------------------------
/samples/scripts/functions.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let myFunctions = functions { name "isaacsuperfun" }
7 |
8 | let deployment = arm {
9 | location Location.NorthEurope
10 | add_resource myFunctions
11 | output "functionsPassword" myFunctions.PublishingPassword
12 | output "functionsAIKey" (myFunctions.AppInsightsKey |> Option.defaultValue ArmExpression.Empty)
13 | output "storageAccountKey" myFunctions.StorageAccountKey
14 | }
15 |
16 | deployment |> Deploy.execute "my-resource-group-name" Deploy.NoParameters
--------------------------------------------------------------------------------
/samples/scripts/iot.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let hub = iotHub {
7 | name "isaacsuperhub"
8 | sku IotHub.B1
9 | capacity 2
10 | partition_count 2
11 | retention_days 3
12 | enable_device_provisioning
13 | }
14 |
15 | let deployment = arm {
16 | location Location.NorthEurope
17 | add_resource hub
18 | output "iot_key" (hub.GetKey IotHub.IotHubOwner)
19 | output "iot_connection" (hub.GetConnectionString IotHub.RegistryReadWrite)
20 | }
21 |
22 | deployment |> Writer.quickWrite "generated-template"
--------------------------------------------------------------------------------
/samples/scripts/tutorials/keyvault-certs-app.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget: Suave, Version=2.6.0"
2 |
3 | open Suave
4 | open System.Security.Cryptography.X509Certificates
5 |
6 | let certWithKey = new X509Certificate2("/certs/key.pfx", "")
7 | let store = new X509Store(StoreName.Root, StoreLocation.CurrentUser)
8 | store.Open(OpenFlags.ReadWrite)
9 | store.Add(certWithKey)
10 | store.Close()
11 |
12 | let config = {
13 | defaultConfig with
14 | bindings = [ HttpBinding.createSimple (HTTPS certWithKey) "0.0.0.0" 443 ]
15 | }
16 |
17 | startWebServer config (Successful.OK "Hello Secure Farmers!")
--------------------------------------------------------------------------------
/src/Farmer/Arm/Webhook.fs:
--------------------------------------------------------------------------------
1 | /// THIS IS A STUB
2 | /// https://learn.microsoft.com/en-us/azure/templates/microsoft.automation/automationaccounts/webhooks
3 | []
4 | module Farmer.Arm.Webhooks
5 |
6 | open Farmer
7 | open System
8 |
9 | let webhooks =
10 | Farmer.ResourceType("Microsoft.Automation/automationAccounts/webhooks", "2015-10-31")
11 |
12 | type Webhook = {
13 | Name: Farmer.ResourceName
14 | } with
15 |
16 | interface Farmer.IArmResource with
17 |
18 | member this.ResourceId = webhooks.resourceId this.Name
19 |
20 | member this.JsonModel = "{}"
--------------------------------------------------------------------------------
/samples/scripts/vm.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.Vm
6 |
7 | let myVm = vm {
8 | name "isaacsVM"
9 | username "isaac"
10 | vm_size Standard_A2
11 | operating_system WindowsServer_2012Datacenter
12 | os_disk 128 StandardSSD_LRS
13 | add_ssd_disk 128
14 | add_slow_disk 512
15 | diagnostics_support
16 | system_identity
17 | }
18 |
19 | let deployment = arm {
20 | location Location.NorthEurope
21 | add_resource myVm
22 | }
23 |
24 | deployment |> Deploy.execute "my-resource-group-name" Deploy.NoParameters
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/maps.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Maps"
3 | date: 2020-05-26T11:24:00+01:00
4 | chapter: false
5 | weight: 13
6 | ---
7 |
8 | #### Overview
9 | The Maps builder creates Azure Maps accounts.
10 |
11 | * Maps (`Microsoft.Maps/accounts`)
12 |
13 | #### Builder Keywords
14 | | Keyword | Purpose |
15 | |-|-|
16 | | name | Sets the name of the Azure Maps account. |
17 | | sku | Sets the sku of the Azure Maps account. |
18 |
19 | #### Example
20 |
21 | ```fsharp
22 | open Farmer
23 | open Farmer.Builders
24 |
25 | let myMaps = maps {
26 | name "mymaps"
27 | sku Maps.S0
28 | }
29 | ```
--------------------------------------------------------------------------------
/src/Farmer/Arm/AutomationAccount.fs:
--------------------------------------------------------------------------------
1 | /// THIS IS A STUB
2 | /// https://learn.microsoft.com/en-us/azure/templates/microsoft.automation/automationaccounts
3 | []
4 | module Farmer.Arm.AutomationAccounts
5 |
6 | open Farmer
7 | open System
8 |
9 | let automationAccounts =
10 | Farmer.ResourceType("Microsoft.Automation/automationAccounts", "2022-08-08")
11 |
12 | type AutomationAccount = {
13 | Name: Farmer.ResourceName
14 | } with
15 |
16 | interface Farmer.IArmResource with
17 |
18 | member this.ResourceId = automationAccounts.resourceId this.Name
19 |
20 | member this.JsonModel = "{}"
--------------------------------------------------------------------------------
/samples/scripts/appinsights-loganalytics.fsx:
--------------------------------------------------------------------------------
1 | #r @"nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let workspace = logAnalytics { name "loganalytics-workspace" }
7 |
8 | let myAppInsights = appInsights {
9 | name "appInsights"
10 | log_analytics_workspace workspace
11 | }
12 |
13 | let myFunctions = functions {
14 | name "functions-app"
15 | link_to_app_insights myAppInsights.Name
16 | }
17 |
18 | let template = arm {
19 | location Location.NorthEurope
20 | add_resources [ workspace; myAppInsights; myFunctions ]
21 | }
22 |
23 | template |> Deploy.execute "deleteme" Deploy.NoParameters
--------------------------------------------------------------------------------
/samples/scripts/databricks.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open System
6 |
7 | let myVault = keyVault { name "my-vault" }
8 |
9 | let workspace = databricks {
10 | name "isaac-databricks"
11 | encrypt_with_key_vault myVault "workspace-encryption-key"
12 | key_vault_key_version Guid.Empty
13 | attach_to_vnet "databricks-vnet" "databricks-pub-snet" "databricks-priv-snet"
14 | }
15 |
16 | let deployment = arm {
17 | location Location.NorthEurope
18 | add_resource workspace
19 | }
20 |
21 | // Generate the ARM template here...
22 | deployment |> Deploy.execute "my-resource-group" []
--------------------------------------------------------------------------------
/samples/scripts/vm-spot-instance.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.Vm
6 |
7 | let myVm = vm {
8 | name "isaacsVM"
9 | username "isaac"
10 | spot_instance Deallocate
11 | vm_size Standard_A2
12 | operating_system WindowsServer_2012Datacenter
13 | os_disk 128 StandardSSD_LRS
14 | add_ssd_disk 128
15 | add_slow_disk 512
16 | diagnostics_support
17 | system_identity
18 | }
19 |
20 | let deployment = arm {
21 | location Location.NorthEurope
22 | add_resource myVm
23 | }
24 |
25 | deployment |> Deploy.execute "my-resource-group-name" Deploy.NoParameters
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## Farmer makes repeatable Azure deployments easy!
4 |
5 | See the full docs [here](https://compositionalit.github.io/farmer).
6 |
7 | Want to edit the docs? Check out the [docs folder](https://github.com/CompositionalIT/farmer/tree/master/docs).
8 |
9 | [](https://compositional-it.visualstudio.com/Farmer/_build/latest?definitionId=14&branchName=master)
10 |
11 | [](https://www.nuget.org/packages/farmer/)
12 |
--------------------------------------------------------------------------------
/src/Tests/test-data/virtual-wan.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "outputs": {},
5 | "parameters": {},
6 | "resources": [
7 | {
8 | "apiVersion": "2020-07-01",
9 | "location": "northeurope",
10 | "name": "farmer-vwan",
11 | "properties": {
12 | "allowBranchToBranchTraffic": true,
13 | "disableVpnEncryption": true,
14 | "office365LocalBreakoutCategory": "None",
15 | "type": "Standard"
16 | },
17 | "type": "Microsoft.Network/virtualWans"
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/src/Farmer/Arm/LogicApps.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.LogicApps
3 |
4 | open Farmer
5 | open System.Text.Json
6 |
7 | let workflows = ResourceType("Microsoft.Logic/workflows", "2019-05-01")
8 |
9 | type LogicApp = {
10 | Name: ResourceName
11 | Location: Location
12 | Definition: JsonDocument
13 | Tags: Map
14 | } with
15 |
16 | interface IArmResource with
17 | member this.ResourceId = workflows.resourceId this.Name
18 |
19 | member this.JsonModel = {|
20 | workflows.Create(this.Name, this.Location, tags = this.Tags) with
21 | properties = this.Definition
22 | |}
--------------------------------------------------------------------------------
/samples/scripts/container-registry.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.ContainerRegistry
6 |
7 | let myRegistry = containerRegistry {
8 | name "devonRegistry"
9 | sku Basic
10 | enable_admin_user
11 | }
12 |
13 | let deployment = arm {
14 | location Location.NorthEurope
15 | add_resource myRegistry
16 | output "registry" myRegistry.Name
17 | output "loginServer" myRegistry.LoginServer
18 | output "user" myRegistry.Username
19 | output "pwd" myRegistry.Password
20 | output "pwd2" myRegistry.Password2
21 | }
22 |
23 | deployment |> Deploy.whatIf "FarmerTest" Deploy.NoParameters |> printfn "%A"
--------------------------------------------------------------------------------
/samples/scripts/nested-resourcegroups.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget: Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let myStorage = storageAccount { name "myfarmerstorage" }
7 |
8 | let myVm = vm {
9 | name "farmer-test-vm"
10 | username "codat"
11 | }
12 |
13 | let nested = resourceGroup {
14 | name "farmer-test-inner"
15 | add_resource myStorage
16 | add_resource myVm
17 | output "foo" "bax"
18 | }
19 |
20 | let template = arm {
21 | location Location.UKSouth
22 | add_resource nested
23 | }
24 |
25 | template |> Writer.quickWrite "template"
26 |
27 | template
28 | |> Deploy.execute "farmer-test-rg" [ ("password-for-farmer-test-vm", "Codat121!") ]
--------------------------------------------------------------------------------
/src/Farmer/Builders/Builders.Helpers.fs:
--------------------------------------------------------------------------------
1 | module Farmer.Helpers
2 |
3 | open System
4 |
5 | let sanitise filters maxLength (resourceName: ResourceName) =
6 | resourceName.Value.ToLower()
7 | |> Seq.filter (fun c -> Seq.exists (fun filter -> filter c) filters)
8 | |> Seq.truncate maxLength
9 | |> Seq.toArray
10 | |> String
11 |
12 | let sanitiseStorage = sanitise [ Char.IsLetterOrDigit ] 24
13 | let sanitiseSearch = sanitise [ Char.IsLetterOrDigit; (=) '-' ] 60
14 | let sanitiseDb = sanitise [ Char.IsLetterOrDigit ] 100 >> fun r -> r.ToLower()
15 | let sanitiseMaps = sanitise [ Char.IsLetterOrDigit; (=) '-'; (=) '.'; (=) '_' ] 98
16 | let sanitiseSignalR = sanitise [ Char.IsLetterOrDigit; (=) '-' ] 63
--------------------------------------------------------------------------------
/samples/scripts/deployment-script.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let createFileScript = deploymentScript {
7 | name "custom-deploy-steps"
8 | force_update
9 | retention_interval 3
10 | env_vars [ EnvVar.createSecureParameter "foo" "secret-foo" ]
11 | supporting_script_uris []
12 | /// Set the script content directly
13 | /// Format output as JSON and pipe to $AZ_SCRIPTS_OUTPUT_PATH to make it available as output.
14 | script_content """printf "{'date':'%s'"} "`date`" > $AZ_SCRIPTS_OUTPUT_PATH """
15 | }
16 |
17 | let template = arm {
18 | add_resource createFileScript
19 | output "date" createFileScript.Outputs.["date"]
20 | }
21 |
22 | template |> Writer.quickWrite "dep-script"
--------------------------------------------------------------------------------
/samples/scripts/tutorials/multiple-web-apps.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let plan = servicePlan {
7 | name "theFarm"
8 | sku WebApp.Sku.F1
9 | }
10 |
11 | let ai = appInsights { name "insights" }
12 |
13 | let planets = [ "jupiter"; "mars"; "pluto"; "venus" ]
14 |
15 | let webApps: IBuilder list = [
16 | for planet in planets do
17 | webApp {
18 | name ("mywebapp-" + planet)
19 | link_to_service_plan plan
20 | link_to_app_insights ai
21 | }
22 | ]
23 |
24 | let template = arm {
25 | location Location.NorthEurope
26 | add_resource plan
27 | add_resource ai
28 | add_resources webApps
29 | }
30 |
31 | template |> Writer.quickWrite "my-resource-group-name"
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/data-lake.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Data Lake"
3 | date: 2020-06-11T00:55:30+02:00
4 | chapter: false
5 | weight: 4
6 | ---
7 |
8 | #### Overview
9 | The Data Lake builder is used to create Azure Data Lake instances.
10 |
11 | * Data Lake (`Microsoft.DataLakeStore/accounts`)
12 |
13 | #### Builder Keywords
14 | | Keyword | Purpose |
15 | |-|-|
16 | | name | Sets the name of the Cognitive Services instance. |
17 | | sku | Sets the SKU of the instance. Defaults to Consumption. |
18 | | enable_encryption | Turns on data lake encryption. |
19 |
20 | #### Example
21 | ```fsharp
22 | open Farmer
23 | open Farmer.Builders
24 |
25 | let myLake = dataLake {
26 | name "myDataLake"
27 | sku DataLake.Commitment_100TB
28 | enable_encryption
29 | }
30 | ```
--------------------------------------------------------------------------------
/src/Farmer/Arm/CommunicationServices.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.Communication
3 |
4 | open Farmer
5 |
6 | let communicationServices =
7 | ResourceType("Microsoft.Communication/communicationServices", "2020-08-20-preview")
8 |
9 | type CommunicationService = {
10 | Name: ResourceName
11 | DataLocation: DataLocation
12 | Tags: Map
13 | } with
14 |
15 | interface IArmResource with
16 | member this.ResourceId = communicationServices.resourceId this.Name
17 |
18 | member this.JsonModel = {|
19 | communicationServices.Create(this.Name, Location.Global, tags = this.Tags) with
20 | properties = {|
21 | dataLocation = this.DataLocation.ArmValue
22 | |}
23 | |}
--------------------------------------------------------------------------------
/src/Farmer/Arm/Maps.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.Maps
3 |
4 | open Farmer
5 | open Farmer.Maps
6 |
7 | let accounts = ResourceType("Microsoft.Maps/accounts", "2018-05-01")
8 |
9 | type Maps = {
10 | Name: ResourceName
11 | Location: Location
12 | Sku: Sku
13 | Tags: Map
14 | } with
15 |
16 | interface IArmResource with
17 | member this.ResourceId = accounts.resourceId this.Name
18 |
19 | member this.JsonModel = {|
20 | accounts.Create(this.Name, this.Location, tags = this.Tags) with
21 | sku = {|
22 | name =
23 | match this.Sku with
24 | | S0 -> "S0"
25 | | S1 -> "S1"
26 | |}
27 | |}
--------------------------------------------------------------------------------
/samples/scripts/container-group.fsx:
--------------------------------------------------------------------------------
1 | #r @"C:\Users\isaac\code\farmer\src\Farmer\bin\Debug\net5.0\Farmer.dll"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.ContainerGroup
6 |
7 | let nginx = containerInstance {
8 | name "nginx"
9 | image "nginx:1.17.6-alpine"
10 | add_ports PublicPort [ 80us; 443us ]
11 | add_ports InternalPort [ 9090us ]
12 | memory 0.5
13 | cpu_cores 1
14 | }
15 |
16 | let profile = networkProfile {
17 | name "netprofile"
18 | vnet "containernet"
19 | subnet "ContainerSubnet"
20 | }
21 |
22 | let g = containerGroup {
23 | name "appWithHttpFrontend"
24 | operating_system Linux
25 | restart_policy AlwaysRestart
26 | add_udp_port 123us
27 | add_instances [ nginx ]
28 | network_profile profile
29 | }
--------------------------------------------------------------------------------
/samples/scripts/tutorials/cosmos-backed-webapp.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.CosmosDb
6 |
7 | let theDatabase = cosmosDb {
8 | name "Tasks"
9 | account_name "isaac-to-do-app-cosmos"
10 | consistency_policy Session
11 | }
12 |
13 | let theWebApp = webApp {
14 | name "isaac-to-do-app"
15 | sku WebApp.Sku.B1
16 | setting "CosmosDb:Account" theDatabase.Endpoint
17 | setting "CosmosDb:Key" theDatabase.PrimaryKey
18 | setting "CosmosDb:DatabaseName" theDatabase.DbName
19 | setting "CosmosDb:ContainerName" "Items"
20 | }
21 |
22 | let template = arm {
23 | location Location.WestEurope
24 | add_resources [ theDatabase; theWebApp ]
25 | }
26 |
27 | template |> Writer.quickWrite @"generated-template"
--------------------------------------------------------------------------------
/samples/scripts/bastion.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | arm {
7 | location Location.EastUS
8 |
9 | add_resources [
10 | vnet {
11 | name "private-network"
12 | add_address_spaces [ "10.1.0.0/16" ]
13 |
14 | add_subnets [
15 | subnet {
16 | name "default"
17 | prefix "10.1.0.0/24"
18 | }
19 | subnet {
20 | name "AzureBastionSubnet"
21 | prefix "10.1.250.0/27"
22 | }
23 | ]
24 | }
25 | bastion {
26 | name "my-bastion-host"
27 | vnet "private-network"
28 | }
29 | ]
30 | }
31 | |> Writer.quickWrite "bastion"
--------------------------------------------------------------------------------
/samples/scripts/cosmos.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.CosmosDb
6 |
7 | let myCosmosDb = cosmosDb {
8 | name "isaacsappdb"
9 | account_name "isaacscosmosdb"
10 | throughput 400
11 | failover_policy NoFailover
12 | consistency_policy (BoundedStaleness(500, 1000))
13 |
14 | add_containers [
15 | cosmosContainer {
16 | name "myContainer"
17 | partition_key [ "/id" ] Hash
18 | add_index "/path" [ Number, Hash ]
19 | exclude_path "/excluded/*"
20 | }
21 | ]
22 | }
23 |
24 | let deployment = arm {
25 | location Location.NorthEurope
26 | add_resource myCosmosDb
27 | }
28 |
29 | deployment |> Deploy.execute "my-resource-group-name" Deploy.NoParameters
--------------------------------------------------------------------------------
/src/Farmer/Arm/CognitiveServices.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.CognitiveServices
3 |
4 | open Farmer
5 |
6 | let accounts = ResourceType("Microsoft.CognitiveServices/accounts", "2017-04-18")
7 |
8 | type Accounts = {
9 | Name: ResourceName
10 | Location: Location
11 | Sku: CognitiveServices.Sku
12 | Kind: CognitiveServices.Kind
13 | Tags: Map
14 | } with
15 |
16 | interface IArmResource with
17 | member this.ResourceId = accounts.resourceId this.Name
18 |
19 | member this.JsonModel = {|
20 | accounts.Create(this.Name, this.Location, tags = this.Tags) with
21 | sku = {| name = string this.Sku |}
22 | kind = this.Kind.ToString().Replace("_", ".")
23 | properties = {| |}
24 | |}
--------------------------------------------------------------------------------
/src/Farmer/Arm/DataLakeStore.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.DataLakeStore
3 |
4 | open Farmer
5 | open Farmer.DataLake
6 |
7 | let accounts = ResourceType("Microsoft.DataLakeStore/accounts", "2016-11-01")
8 |
9 | type Account = {
10 | Name: ResourceName
11 | Location: Location
12 | EncryptionState: FeatureFlag
13 | Sku: Sku
14 | Tags: Map
15 | } with
16 |
17 | interface IArmResource with
18 | member this.ResourceId = accounts.resourceId this.Name
19 |
20 | member this.JsonModel = {|
21 | accounts.Create(this.Name, this.Location, tags = this.Tags) with
22 | properties = {|
23 | newTier = this.Sku.ToString()
24 | encryptionState = this.EncryptionState.ToString()
25 | |}
26 | |}
--------------------------------------------------------------------------------
/src/Farmer/Arm/ContainerRegistry.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.ContainerRegistry
3 |
4 | open Farmer
5 | open Farmer.ContainerRegistry
6 |
7 | let registries =
8 | ResourceType("Microsoft.ContainerRegistry/registries", "2019-05-01")
9 |
10 | type Registries = {
11 | Name: ResourceName
12 | Location: Location
13 | Sku: Sku
14 | AdminUserEnabled: bool
15 | Tags: Map
16 | } with
17 |
18 | interface IArmResource with
19 | member this.ResourceId = registries.resourceId this.Name
20 |
21 | member this.JsonModel = {|
22 | registries.Create(this.Name, this.Location, tags = this.Tags) with
23 | sku = {| name = this.Sku.ToString() |}
24 | properties = {|
25 | adminUserEnabled = this.AdminUserEnabled
26 | |}
27 | |}
--------------------------------------------------------------------------------
/samples/scripts/eventgrid.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | /// Monitor this storage account
7 | let storageSource = storageAccount {
8 | name "isaacstorageacc"
9 | add_private_container "data"
10 | }
11 |
12 | /// Send events to this event hub
13 | let destinationHub = eventHub {
14 | name "isaachub"
15 | namespace_name "isaacns"
16 | }
17 |
18 | /// Tie them together using event grid.
19 | let eventHubGrid = eventGrid {
20 | topic_name "isaacHubTopic"
21 | source storageSource
22 | add_eventhub_subscriber destinationHub [ SystemEvents.Storage.BlobCreated; SystemEvents.Storage.BlobDeleted ]
23 | }
24 |
25 | let deployment = arm { add_resources [ storageSource; eventHubGrid; destinationHub ] }
26 |
27 | // Generate the ARM template here...
28 | deployment |> Writer.quickWrite "farmer-deploy"
--------------------------------------------------------------------------------
/samples/scripts/tutorials/custom-output.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let er = expressRoute {
7 | name "my-test-circuit"
8 | service_provider "Equinix"
9 | peering_location "Frankfurt"
10 | }
11 |
12 | // Build an ARM resourceId type for the circuit.
13 | let erId = ResourceId.create (Arm.Network.expressRouteCircuits, er.Name)
14 |
15 | // Use that ID to build a reference expression and get a property of the referenced resource.
16 | let serviceKeyRef =
17 | ArmExpression.create ($"reference({erId.ArmExpression.Value}).serviceKey")
18 |
19 | // That reference can be used in the output for the template so that ARM can populate it from the newly deployed resource
20 | arm {
21 | location Location.WestEurope
22 | add_resource er
23 | output "er-service-key" serviceKeyRef
24 | }
25 | |> Writer.quickWrite "custom-output"
--------------------------------------------------------------------------------
/src/Farmer/Arm/BingSearch.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.BingSearch
3 |
4 | open Farmer
5 |
6 | let accounts = ResourceType("Microsoft.Bing/accounts", "2020-06-10")
7 |
8 | []
9 | let private kind = "Bing.Search.v7"
10 |
11 | type Accounts = {
12 | Name: ResourceName
13 | Location: Location
14 | Sku: BingSearch.Sku
15 | Tags: Map
16 | Statistics: FeatureFlag
17 | } with
18 |
19 | interface IArmResource with
20 | member this.ResourceId = accounts.resourceId this.Name
21 |
22 | member this.JsonModel = {|
23 | accounts.Create(this.Name, this.Location, tags = this.Tags) with
24 | sku = {| name = string this.Sku |}
25 | kind = kind
26 | properties = {|
27 | statisticsEnabled = this.Statistics.AsBoolean
28 | |}
29 | |}
--------------------------------------------------------------------------------
/samples/scripts/postgresql.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget: Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.PostgreSQL
6 |
7 | let myPostgres = postgreSQL {
8 | name "flexibleserver"
9 | admin_username "adminallthethings"
10 | storage_size 64
11 | storage_performance_tier Vm.DiskPerformanceTier.P10
12 | tier FlexibleTier.Burstable_B1ms
13 | enable_azure_firewall
14 | storage_autogrow true
15 |
16 | add_database (
17 | postgreSQLDb {
18 | name "thedatabase"
19 | collation "en_US.utf8"
20 | }
21 | )
22 | }
23 |
24 | let template = arm {
25 | location Location.NorthEurope
26 | add_resource myPostgres
27 | }
28 |
29 | // WARNING:
30 | // since there is currently no free tier for PostgreSQL, actually deploying this
31 | // *will* incur spending on your subscription.
32 | template |> Writer.quickWrite "postgres-example"
--------------------------------------------------------------------------------
/pull_request_template.md:
--------------------------------------------------------------------------------
1 | This PR closes #
2 |
3 | The changes in this PR are as follows:
4 |
5 | * ...
6 | * ...
7 | * ...
8 |
9 | I have read the [contributing guidelines](CONTRIBUTING.md) and have completed the following:
10 |
11 | * [ ] **Tested my code** end-to-end against a live Azure subscription.
12 | * [ ] **Updated the documentation** in the docs folder for the affected changes.
13 | * [ ] **Written unit tests** against the modified code that I have made.
14 | * [ ] **Updated the [release notes](RELEASE_NOTES.md)** with a new entry for this PR.
15 | * [ ] **Checked the coding standards** outlined in the [contributions guide](CONTRIBUTING.md) and ensured my code adheres to them.
16 |
17 | If I haven't completed any of the tasks above, I include the reasons why here:
18 |
19 | Below is a minimal example configuration that includes the new features, which can be used to deploy to Azure:
20 |
21 | ```fsharp
22 | ```
23 |
--------------------------------------------------------------------------------
/samples/scripts/eventhubs.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.EventHub
6 |
7 | let myEh = eventHub {
8 | name "first-hub"
9 | namespace_name "allmyevents"
10 |
11 | sku Standard
12 | enable_zone_redundant
13 | enable_auto_inflate 3
14 | add_authorization_rule "FirstRule" [ Listen; Send ]
15 | add_authorization_rule "SecondRule" AllAuthorizationRights
16 |
17 | partitions 2
18 | message_retention_days 3
19 | add_consumer_group "myGroup"
20 | }
21 |
22 | let secondHub = eventHub {
23 | name "second-hub"
24 | link_to_namespace "allmyevents"
25 | partitions 1
26 | message_retention_days 1
27 | }
28 |
29 | let deployment = arm {
30 | location Location.NorthEurope
31 | add_resource myEh
32 | add_resource secondHub
33 | }
34 |
35 | // Generate the ARM template here...
36 | deployment |> Writer.quickWrite "farmer-deploy"
--------------------------------------------------------------------------------
/src/Tests/RoleAssignment.fs:
--------------------------------------------------------------------------------
1 | module RoleAssignment
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.Arm
6 |
7 | let tests =
8 | testList "RoleAssignment" [
9 | test "Produces opaque resource scope" {
10 | let actual: IArmResource = {
11 | Name = ResourceName "assignment"
12 | RoleDefinitionId = Roles.Contributor
13 | PrincipalId = ArmExpression.create "1" |> PrincipalId
14 | PrincipalType = PrincipalType.User
15 | Scope = privateClouds.resourceId "mySDDC" |> UnmanagedResource
16 | Dependencies = Set.empty
17 | }
18 |
19 | "Expected matching scope"
20 | |> Expect.stringContains
21 | (Newtonsoft.Json.JsonConvert.SerializeObject actual.JsonModel)
22 | "\"scope\":\"[resourceId('Microsoft.AVS/privateClouds', 'mySDDC')]\""
23 | }
24 | ]
--------------------------------------------------------------------------------
/samples/scripts/operations-management.fsx:
--------------------------------------------------------------------------------
1 | #r "../../src/Farmer/bin/Debug/netstandard2.0/Farmer.dll"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let sentinelWorkspace = logAnalytics {
7 | name "my-sentinel-workspace"
8 | retention_period 30
9 | enable_query
10 | daily_cap 5
11 | }
12 |
13 | let omsName = $"SecurityInsights({sentinelWorkspace.Name.Value})"
14 |
15 | let sentinelSolution = oms {
16 | name omsName
17 |
18 | plan (
19 | omsPlan {
20 | name omsName
21 | publisher "Microsoft"
22 | product "OMSGallery/SecurityInsights"
23 | }
24 | )
25 |
26 | properties (omsProperties { workspace sentinelWorkspace })
27 | }
28 |
29 | let deployment = arm {
30 | location Location.NorthCentralUS
31 | add_resource sentinelWorkspace
32 | add_resource sentinelSolution
33 | }
34 |
35 | deployment |> Writer.quickWrite "operationsManagement"
--------------------------------------------------------------------------------
/samples/scripts/logicapp.fsx:
--------------------------------------------------------------------------------
1 | #r "../../src/Farmer/bin/Debug/netstandard2.0/Farmer.dll"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.Arm.LogicApps
6 |
7 | let emptyLogicApp =
8 | """
9 | {
10 | "definition": {
11 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
12 | "actions": {},
13 | "contentVersion": "1.0.0.0",
14 | "outputs": {},
15 | "parameters": {},
16 | "triggers": {}
17 | },
18 | "parameters": {}
19 | }
20 | """
21 |
22 | let myValueLogicApp = logicApp {
23 | name "value-test-logic-app"
24 | definition (ValueDefinition emptyLogicApp)
25 | add_tags [ ("environment", "dev"); ("created-by", "farmer") ]
26 | }
27 |
28 | let deployment = arm {
29 | location Location.CentralUS
30 | add_resource myValueLogicApp
31 | }
32 |
33 | deployment |> Writer.quickWrite "logicApp"
--------------------------------------------------------------------------------
/src/Tests/Maps.fs:
--------------------------------------------------------------------------------
1 | module Maps
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.Builders
6 | open Farmer.Maps
7 | open System
8 | open Microsoft.Azure.Management.Maps
9 | open Microsoft.Azure.Management.Maps.Models
10 | open Microsoft.Rest
11 |
12 | let client =
13 | new MapsManagementClient(Uri "http://management.azure.com", TokenCredentials "NotNullOrWhiteSpace")
14 |
15 | let tests =
16 | testList "Maps" [
17 | test "Can create a basic maps account" {
18 | let resource =
19 | let account = maps {
20 | name "mymaps~@"
21 | sku S0
22 | }
23 |
24 | arm { add_resource account }
25 | |> findAzureResources client.SerializationSettings
26 | |> List.head
27 |
28 | resource.Validate()
29 | Expect.equal resource.Name "mymaps" ""
30 | Expect.equal resource.Sku.Name "S0" ""
31 | }
32 | ]
--------------------------------------------------------------------------------
/samples/scripts/keyvault-keys.fsx:
--------------------------------------------------------------------------------
1 | #r @"../../src/Farmer/bin/Debug/net5.0/Farmer.dll"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.KeyVault
6 |
7 | let vault = keyVault {
8 | name "TestFarmVault"
9 | sku Sku.Standard
10 | tenant_id Subscription.TenantId
11 | add_tag "test" "test"
12 |
13 | add_keys [
14 | key {
15 | name "testKeyInline1"
16 | key_type KeyType.RSA_4096
17 | }
18 | key {
19 | name "testKeyInline2"
20 | key_type KeyType.EC_P256
21 | }
22 | ]
23 | }
24 |
25 |
26 | let myKey = key {
27 | name "testKey3"
28 | key_type KeyType.RSA_4096
29 | link_to_unmanaged_keyvault vault
30 | key_operations [ KeyOperation.Encrypt ]
31 | }
32 |
33 | let deployment = arm {
34 | location Location.EastUS
35 | add_resource vault
36 | add_resource myKey
37 | }
38 |
39 | deployment
40 | |> Writer.quickWrite (System.IO.Path.GetFileNameWithoutExtension __SOURCE_FILE__)
--------------------------------------------------------------------------------
/samples/scripts/cdn.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let isaacWebApp = webApp {
7 | name "isaacsuperweb"
8 | app_insights_off
9 | }
10 |
11 | let isaacStorage = storageAccount { name "isaacsuperstore" }
12 |
13 | let isaacCdn = cdn {
14 | name "isaacsupercdn"
15 |
16 | add_endpoints [
17 | endpoint {
18 | origin isaacStorage
19 | optimise_for Cdn.OptimizationType.LargeFileDownload
20 | }
21 | endpoint {
22 | origin isaacWebApp
23 | disable_http
24 | }
25 | endpoint {
26 | name "custom-endpoint-name"
27 | origin "mysite.com"
28 | add_compressed_content [ "text/plain"; "text/html"; "text/css" ]
29 | query_string_caching_behaviour Cdn.BypassCaching
30 | }
31 | ]
32 | }
33 |
34 | let deployment = arm { add_resources [ isaacStorage; isaacCdn; isaacWebApp ] }
35 |
36 | deployment |> Writer.quickWrite "generated-template"
--------------------------------------------------------------------------------
/samples/scripts/tutorials/serverless-etl.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let database = sqlDb {
7 | name "isaacparseddata"
8 | sku Sql.DtuSku.S1
9 | }
10 |
11 | let transactionalDb = sqlServer {
12 | name "isaacetlserver"
13 | admin_username "theadministrator"
14 | add_databases [ database ]
15 | }
16 |
17 | let etlProcessor = functions {
18 | name "isaacetlprocessor"
19 | storage_account_name "isaacmydata"
20 | setting "sql-conn" (transactionalDb.ConnectionString database)
21 | }
22 |
23 | let template = arm {
24 | location Location.WestEurope
25 | add_resources [ transactionalDb; etlProcessor ]
26 | }
27 |
28 | // Generate the ARM template here...
29 | template |> Writer.quickWrite @"generated-template"
30 |
31 | // Or deploy it directly to Azure here... (required Azure CLI installed!)
32 | // template
33 | // |> Deploy.execute "my-resource-group" [ transactionalDb.PasswordParameter, "SQL PASSWORD GOES HERE" ]
34 | // |> printfn "%A"
--------------------------------------------------------------------------------
/docs/config.toml:
--------------------------------------------------------------------------------
1 | baseURL = "https://compositionalit.github.io/farmer/"
2 | languageCode = "en-gb"
3 | title = "Farmer"
4 | author = "Compositional IT"
5 | theme = "hugo-theme-learn"
6 | [outputs]
7 | relativeUrls = true
8 | home = [ "HTML", "RSS", "JSON"]
9 | [params]
10 | themeVariant="green"
11 | editURL = "https://github.com/CompositionalIT/farmer/tree/master/docs/content/"
12 | description = "Farmer - Making repeatable Azure deployments easy!"
13 |
14 | [[Languages.en.menu.shortcuts]]
15 | name = " GitHub repo"
16 | identifier = "ds"
17 | url = "https://github.com/compositionalit/farmer"
18 | weight = 10
19 |
20 | [[Languages.en.menu.shortcuts]]
21 | name = " Nuget"
22 | identifier = "nuget"
23 | url = "https://www.nuget.org/packages/Farmer/"
24 | weight = 10
25 |
26 | [[Languages.en.menu.shortcuts]]
27 | name = "
Compositional IT"
28 | identifier = "cit"
29 | url = "https://compositional-it.com"
30 | weight = 11
31 |
--------------------------------------------------------------------------------
/samples/scripts/container-instance-gpu.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.ContainerGroup
6 |
7 | let template = arm {
8 | location Location.WestEurope
9 |
10 | add_resources [
11 | containerGroup {
12 | name "container-group-with-gpu"
13 | operating_system Linux
14 | restart_policy ContainerGroup.RestartOnFailure
15 |
16 | add_instances [
17 | containerInstance {
18 | name "gpucontainer"
19 | image "mcr.microsoft.com/azuredocs/samples-tf-mnist-demo:gpu"
20 | memory 12.0
21 | cpu_cores 4.0
22 |
23 | gpu (
24 | containerInstanceGpu {
25 | count 1
26 | sku Gpu.V100
27 | }
28 | )
29 | }
30 | ]
31 | }
32 | ]
33 | }
34 |
35 | Writer.quickWrite "container-instance" template
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/search.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Search"
3 | date: 2020-02-05T08:53:46+01:00
4 | chapter: false
5 | weight: 18
6 | ---
7 |
8 | #### Overview
9 | The Search builder creates storage accounts and their associated containers.
10 |
11 | * Search (`Microsoft.Search/searchServices`)
12 |
13 | #### Builder Keywords
14 | | Keyword | Purpose |
15 | |-|-|
16 | | name | Sets the name of the Azure Search instance. |
17 | | sku | Sets the sku of the Azure Search instance. |
18 | | replicas | Sets the replica count of the Azure Search instance. |
19 | | partitions | Sets the number of partitions of the Azure Search instance. |
20 |
21 | #### Configuration Members
22 |
23 | | Member | Purpose |
24 | |-|-|
25 | | AdminKey | Gets an ARM expression for the admin key of the search instance. |
26 | | QueryKey | Gets an ARM expression for the query key of the search instance. |
27 |
28 | #### Example
29 |
30 | ```fsharp
31 | open Farmer
32 | open Farmer.Builders
33 |
34 | let mySearch = search {
35 | name "isaacsSearch"
36 | sku Search.Basic
37 | }
38 | ```
--------------------------------------------------------------------------------
/samples/scripts/tutorials/webapp-keyvault.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let secretName = "storagekey"
7 | let vaultName = "isaacsupersecret"
8 |
9 | let datastore = storageAccount { name "isaacsuperstore" }
10 |
11 | let webapplication = webApp {
12 | name "isaacsuperweb"
13 | system_identity
14 | link_to_keyvault (ResourceName vaultName)
15 | secret_setting secretName
16 | }
17 |
18 | let secretsvault = keyVault {
19 | name vaultName
20 | add_secret (secretName, datastore.Key)
21 | add_access_policy (AccessPolicy.create webapplication.SystemIdentity)
22 | }
23 |
24 | let template = arm {
25 | location Location.WestEurope
26 | add_resources [ secretsvault; datastore; webapplication ]
27 | }
28 |
29 | // Generate the ARM template here...
30 | template |> Writer.quickWrite (__SOURCE_DIRECTORY__ + @"/generated-template")
31 |
32 | // Or deploy it directly to Azure here... (required Azure CLI installed!)
33 | // template
34 | // |> Deploy.execute "my-resource-group" Deploy.NoParameters
35 | // |> printfn "%A"
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/bing-search.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Bing Search"
3 | date: 2021-01-29T07:33:46+01:00
4 | chapter: false
5 | weight: 2
6 | ---
7 |
8 | #### Overview
9 | The Bing Search builder is used to create Azure Bing Search instances.
10 |
11 | * Bing Search (`Microsoft.Bing/accounts`, kind: `Bing.Search.v7`)
12 |
13 | #### Builder Keywords
14 | | Keyword | Purpose |
15 | |-|-|
16 | | name | Sets the name of the Bing Search instance. |
17 | | sku | Sets the SKU of the instance. Defaults to `F1` (free). |
18 | | statistics | Sets the `statisticsEnabled` property of the instance. Defaults to `false` |
19 |
20 | #### Configuration Members
21 |
22 | | Member | Purpose |
23 | |-|-|
24 | | Key | Gets the ARM expression path to the Key of this Bing Search instance. |
25 |
26 | #### Example
27 | ```fsharp
28 | open Farmer
29 | open Farmer.Builders
30 |
31 | let tags = [ "a", "1"; "b", "2" ]
32 | let translator = bingSearch {
33 | name "test"
34 | sku S0
35 | add_tags tags
36 | statistics Enabled
37 | }
38 |
39 | let key : ArmExpression = translator.Key
40 | ```
--------------------------------------------------------------------------------
/samples/scripts/webapp-storage.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let myStorage = storageAccount {
7 | name "mystorage"
8 | sku Storage.Sku.Standard_LRS
9 | add_lifecycle_rule "cleanup" [ Storage.DeleteAfter 7 ] Storage.NoRuleFilters
10 |
11 | add_lifecycle_rule "test" [
12 | Storage.DeleteAfter 1
13 | Storage.DeleteAfter 2
14 | Storage.ArchiveAfter 1
15 | ] [ "foo/bar" ]
16 | }
17 |
18 | let myWebApp = webApp {
19 | name "mysuperwebapp"
20 | sku WebApp.Sku.S1
21 | app_insights_off
22 | setting "storage_key" myStorage.Key
23 | add_allowed_ip_restriction "allow everything" "0.0.0.0/0"
24 | add_denied_ip_restriction "deny" "1.2.3.4/31"
25 | }
26 |
27 | let deployment = arm {
28 | location Location.NorthEurope
29 | add_resource myStorage
30 | add_resource myWebApp
31 | output "storage_key" myStorage.Key
32 | output "web_password" myWebApp.PublishingPassword
33 | }
34 |
35 | deployment |> Deploy.execute "my-resource-group-name" Deploy.NoParameters
--------------------------------------------------------------------------------
/samples/scripts/service-bus.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.ServiceBus
6 |
7 | let myServiceBus = serviceBus {
8 | name "allMyQueues"
9 | sku Standard
10 | min_tls_version TlsVersion.Tls12
11 | enable_zone_redundancy
12 | disable_public_network_access
13 | add_queues [ queue { name "queuenumberone" }; queue { name "queuenumbertwo" } ]
14 |
15 | add_topics [
16 | topic {
17 | name "thetopic"
18 |
19 | add_subscriptions [
20 | subscription {
21 | name "thesub"
22 | forward_to "queuenumberone"
23 | }
24 | ]
25 | }
26 | ]
27 | }
28 |
29 | let deployment = arm {
30 | location Location.NorthEurope
31 | add_resource myServiceBus
32 | output "NamespaceDefaultConnectionString" myServiceBus.NamespaceDefaultConnectionString
33 | output "DefaultSharedAccessPolicyPrimaryKey" myServiceBus.DefaultSharedAccessPolicyPrimaryKey
34 | }
35 |
36 | deployment |> Deploy.execute "service-bus-test" []
--------------------------------------------------------------------------------
/samples/scripts/tutorials/aci-fsharp.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget: Farmer"
2 |
3 | let script =
4 | """
5 | #r "nuget: Suave, Version=2.6.0"
6 | open Suave
7 | let config = { defaultConfig with bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" 8080 ] }
8 | startWebServer config (Successful.OK "Hello Farmers!")
9 | """
10 |
11 | open Farmer
12 | open Farmer.Builders
13 |
14 | let containers = containerGroup {
15 | name "my-app"
16 |
17 | add_instances [
18 | containerInstance {
19 | name "fsi"
20 | image "mcr.microsoft.com/dotnet/sdk:5.0"
21 | command_line ("dotnet fsi /src/main.fsx".Split null |> List.ofArray)
22 | add_volume_mount "script-source" "/src"
23 | add_public_ports [ 8080us ]
24 | cpu_cores 0.2
25 | memory 0.5
26 | }
27 | ]
28 |
29 | public_dns "my-app-fsi-suave" [ TCP, 8080us ]
30 | add_volumes [ volume_mount.secret_string "script-source" "main.fsx" script ]
31 | }
32 |
33 | arm {
34 | location Location.EastUS
35 | add_resources [ containers ]
36 | }
37 | |> Writer.quickWrite "aci-fsharp"
--------------------------------------------------------------------------------
/samples/scripts/safe-template.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let makeSafeApp (environment: string) theLocation storageSku webAppSku =
7 | let environment = environment.ToLower()
8 | let generateResourceName = sprintf "safe-%s-%s" environment
9 |
10 | let myStorageAccount = storageAccount {
11 | name (sprintf "safe%sstorage" environment)
12 | sku storageSku
13 | }
14 |
15 | let myWebApp = webApp {
16 | name (generateResourceName "web")
17 | sku webAppSku
18 |
19 | website_node_default_version "8.1.4"
20 | setting "public_path" "./public"
21 | setting "STORAGE_CONNECTIONSTRING" myStorageAccount.Key
22 | }
23 |
24 | arm {
25 | location theLocation
26 |
27 | add_resource myStorageAccount
28 | add_resource myWebApp
29 |
30 | output "webAppName" myWebApp.Name
31 | output "webAppPassword" myWebApp.PublishingPassword
32 | }
33 |
34 | makeSafeApp "dev" Location.NorthEurope Storage.Standard_LRS WebApp.Sku.F1
35 | |> Deploy.execute "my-resource-group-name" Deploy.NoParameters
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Compositional IT
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/samples/scripts/eventgrid-fn.fsx:
--------------------------------------------------------------------------------
1 | #r "../../src/Farmer/bin/Debug/netstandard2.0/Farmer.dll"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | /// Send events to this function that was deployed separately
7 | let fnRef =
8 | {
9 | Arm.Web.siteFunctions.resourceId (ResourceName "gridFnApp", ResourceName "eventHandler") with
10 | ResourceGroup = Some "fn-rg"
11 | }
12 | |> Unmanaged
13 |
14 | /// The source will default to the resourceGroup() and event grid target will be the function handler.
15 | let grid = eventGrid {
16 | topic_name "src-rg-events"
17 |
18 | add_function_subscriber
19 | fnRef
20 | {
21 | MaxEventsPerBatch = 1u
22 | PreferredBatchSizeInKilobytes = 64u
23 | }
24 | [
25 | SystemEvents.Resources.ResourceWriteSuccess
26 | SystemEvents.Resources.ResourceActionSuccess
27 | ]
28 | }
29 |
30 | // deploy into the resource group that we want to be the source of events
31 | let deployment = arm { add_resources [ grid ] }
32 |
33 | // Generate the ARM template here...
34 | deployment |> Writer.quickWrite "farmer-deploy"
--------------------------------------------------------------------------------
/docs/content/support/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Commercial Support"
3 | date: 2020-06-27T17:57:05+02:00
4 | weight: 7
5 | ---
6 |
7 | The creators of Farmer, [Compositional IT](https://compositional-it.com), offer a professional [fully managed support package](https://www.compositional-it.com/consultancy/farmer/) which we strongly recommend for any organisations using Farmer on a commercial basis. It includes:
8 |
9 | * **Prioritised resolution** of any bugs. If you find a bug that's blocking you, we'll prioritise it and release a hot fix as soon as it's ready.
10 | * **Prioritised resolution** and escalation of issues. If there's a possible issue or question, we'll prioritise dealing with it.
11 | * **Prioritised feature requests**: Get new features that are important to you added first.
12 | * **Personalised support** and guidance via email, telephone or video. Speak to one of our team for advice and best practices on how to best manage deployments.
13 | * **Discounts** on our [F# and Azure training and coaching services](https://www.compositional-it.com/training-coaching/)
14 |
15 | #### Please [contact us](mailto:info@compositional-it.com) to find out more!
--------------------------------------------------------------------------------
/samples/scripts/vnet-gateway.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.VirtualNetworkGateway
6 | open Farmer.Arm.Network
7 |
8 | let privateNet = vnet {
9 | name "my-vnet"
10 | add_address_spaces [ "10.30.0.0/16" ]
11 |
12 | add_subnets [
13 | subnet {
14 | name "GatewaySubnet"
15 | prefix "10.30.254.0/28"
16 | }
17 | ]
18 | }
19 |
20 | /// In case you need to specify public IP details, create a public IP
21 | /// and assign it to the gateway with 'gateway_ip_config'
22 | let gatewayIp = {
23 | Name = ResourceName "gw-pip"
24 | Location = Location.NorthEurope
25 | DomainNameLabel = Some "mygateway"
26 | }
27 |
28 | let myGateway = gateway {
29 | name "er-gateway"
30 | er_gateway_sku ErGatewaySku.Standard
31 | vpn_type VpnType.RouteBased
32 | vnet "my-vnet"
33 | gateway_ip_config DynamicPrivateIp gatewayIp
34 | }
35 |
36 | let deployment = arm {
37 | location Location.NorthEurope
38 | add_resource privateNet
39 | add_resource myGateway
40 | add_resource gatewayIp
41 | }
42 |
43 | deployment |> Deploy.whatIf "FarmerTest" Deploy.NoParameters |> printfn "%A"
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/communication-services.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Communication Services"
3 | date: 2021-04-28T23:33:46+01:00
4 | chapter: false
5 | weight: 3
6 | ---
7 |
8 | #### Overview
9 | The Communication Services builder is used to create Azure Communication Services instances.
10 |
11 | * Communication Services (`Microsoft.Communication/communicationServices`)
12 |
13 | #### Builder Keywords
14 | | Keyword | Purpose |
15 | |-|-|
16 | | name | Sets the name of the Communication Services instance. |
17 | | data_location | Sets the `dataLocation` property of the instance. Defaults to `United States` |
18 |
19 | #### Configuration Members
20 |
21 | | Member | Purpose |
22 | |-|-|
23 | | Key | Gets the ARM expression path to the Key of this Communication Services instance. |
24 | | Key | Gets the ARM expression path to the Connection String of this Communication Services instance. |
25 |
26 | #### Example
27 | ```fsharp
28 | open Farmer
29 | open Farmer.Builders
30 |
31 | let tags = [ "a", "1"; "b", "2" ]
32 | let cs = communicationService {
33 | name "test"
34 | add_tags tags
35 | data_location DataLocation.Australia
36 | }
37 |
38 | let key : ArmExpression = cs.Key
39 | ```
--------------------------------------------------------------------------------
/samples/scripts/diagnosticsetting.fsx:
--------------------------------------------------------------------------------
1 | #r "./libs/Newtonsoft.Json.dll"
2 | #r "../../src/Farmer/bin/Debug/netstandard2.0/Farmer.dll"
3 |
4 | open Farmer
5 | open Farmer.Builders
6 | open Farmer.DiagnosticSettings
7 |
8 | let data = storageAccount { name "isaacsuperdata" }
9 | let hub = eventHub { name "isaacsuperhub" }
10 | let logs = logAnalytics { name "isaacsuperlogs" }
11 |
12 | let web = webApp {
13 | name "isaacdiagsuperweb"
14 | app_insights_off
15 | }
16 |
17 | let mydiagnosticSetting = diagnosticSettings {
18 | name "myDiagnosticSetting"
19 | metrics_source web
20 |
21 | add_destination data
22 | add_destination logs
23 | add_destination hub
24 | loganalytics_output_type Dedicated
25 | capture_metrics [ "AllMetrics" ]
26 |
27 | capture_logs [
28 | Logging.Web.Sites.AppServicePlatformLogs
29 | Logging.Web.Sites.AppServiceAntivirusScanAuditLogs
30 | Logging.Web.Sites.AppServiceAppLogs
31 | Logging.Web.Sites.AppServiceHTTPLogs
32 | ]
33 |
34 | add_tag "sample" "isaac"
35 | }
36 |
37 | let deployment = arm { add_resources [ data; web; hub; logs; mydiagnosticSetting ] }
38 |
39 | deployment |> Writer.quickWrite "diagnostics"
40 |
41 | deployment |> Deploy.execute "isaacdiagtest" []
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/cognitive-services.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Cognitive Services"
3 | date: 2020-04-10T08:53:46+01:00
4 | chapter: false
5 | weight: 3
6 | ---
7 |
8 | #### Overview
9 | The Cognitive Services builder is used to create Azure Cognitive Services instances.
10 |
11 | * Cognitive Services (`Microsoft.CognitiveServices/accounts`)
12 |
13 | #### Builder Keywords
14 | | Keyword | Purpose |
15 | |-|-|
16 | | name | Sets the name of the Cognitive Services instance. |
17 | | sku | Sets the SKU of the instance. Defaults to F0 (free). |
18 | | api | Specifies the Kind of api to use for the service instance. Defaults to `AllInOne`. |
19 |
20 | #### Configuration Members
21 |
22 | | Member | Purpose |
23 | |-|-|
24 | | Key | Gets the ARM expression path to the Key of this Cognitive Services instance. |
25 |
26 | #### Example
27 | ```fsharp
28 | open Farmer
29 | open Farmer.Builders
30 |
31 | let translator = cognitiveServices {
32 | name "mytranslator"
33 | sku CognitiveServices.F0
34 | api CognitiveServices.AnomalyDetector
35 | }
36 |
37 | let key : ArmExpression = translator.Key
38 | ```
39 |
40 | ### BingSearch (obsolete)
41 |
42 | Starting from 1.4.0 BingSearch api is available as a part of `bingSearch` builder instead of `cognitiveServices`.
--------------------------------------------------------------------------------
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: gh-pages
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | paths:
7 | - 'docs/**'
8 |
9 | jobs:
10 | deploy:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout master branch
14 | uses: actions/checkout@v2
15 | with:
16 | ref: master # Pull the master branch
17 | submodules: true # Fetch Hugo themes as well (they're in sub-modules)
18 | fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
19 |
20 | - name: Setup Hugo
21 | uses: peaceiris/actions-hugo@v2
22 | with:
23 | hugo-version: "0.68.3"
24 |
25 | # Builds using hugo; outputs to ./public by default, but ./ is ./docs here,
26 | # because of the working-directory setting, so the output will be in ./docs/public
27 | # this is then used for `publish_dir` in the Deploy step below
28 | - name: Build
29 | working-directory: ./docs
30 | run: hugo --minify
31 |
32 | - name: Deploy # Pushes output of the build to the GH Pages branch
33 | uses: peaceiris/actions-gh-pages@v3
34 | with:
35 | commit_message: ${{ github.event.head_commit.message }}
36 | github_token: ${{ secrets.GITHUB_TOKEN }}
37 | publish_dir: ./docs/public
38 |
--------------------------------------------------------------------------------
/samples/scripts/storage.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let myStorage = storageAccount {
7 | name "myfarmerstorage"
8 | sku Storage.Sku.Standard_LRS
9 |
10 | add_queues [
11 | storageQueue {
12 | name "queue1"
13 | metadata [ "environment", "dev"; "project", "farmer" ]
14 | }
15 | storageQueue {
16 | name "queue2"
17 | metadata [ "environment", "test"; "project", "barnyard" ]
18 | }
19 | ]
20 |
21 | add_private_container "container1"
22 | add_table "table1"
23 | add_tables [ "table2"; "table3" ]
24 | add_lifecycle_rule "cleanup" [ Storage.DeleteAfter 7 ] Storage.NoRuleFilters
25 |
26 | add_lifecycle_rule "test" [
27 | Storage.DeleteAfter 1
28 | Storage.DeleteAfter 2
29 | Storage.ArchiveAfter 1
30 | ] [ "foo/bar" ]
31 |
32 | disable_public_network_access
33 | disable_blob_public_access
34 | disable_shared_key_access
35 | default_to_oauth_authentication
36 | restrict_to_azure_services [ Farmer.Arm.Storage.NetworkRuleSetBypass.AzureServices ]
37 | }
38 |
39 | let template = arm { add_resource myStorage }
40 |
41 | template |> Writer.quickWrite "template"
42 | template |> Deploy.execute "farmer-test-rg" []
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/virtual-hub.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Virtual Hub"
3 | date: 2021-07-07T08:53:46+01:00
4 | chapter: false
5 | weight: 21
6 | ---
7 |
8 | #### Overview
9 | The Virtual WAN builder (`vhub`) is used to create Azure Virtual Hub instances.
10 |
11 | - Virtual Hub (`Microsoft.Network/virtualHubs`)
12 |
13 | #### Builder Keywords
14 |
15 | | Resource | Keyword | Purpose |
16 | | -------------- | -------------------- | -----------------------------------------------------------------------|
17 | | vhub | name | Sets the name of the virtual hub |
18 | | vhub | sku | Sets the sku of the virtual hub |
19 | | vhub | address_prefix | Sets the address prefix of the virtual hub |
20 | | vhub | link_to_vwan | Sets the virtual wan deployed by Farmer to which the virtual hub belongs |
21 | | vhub | link_to_unmanaged_vwan | Sets the existing virtual wan to which the virtual hub belongs |
22 |
23 | ### Example
24 |
25 | ```fsharp
26 | open Farmer
27 | open Farmer.Builders
28 |
29 | let vhub = vhub {
30 | name "my-vhub"
31 | address_prefix (IPAddressCidr.parse "10.0.0.0/24")
32 | }
33 |
34 | let deployment = arm {
35 | location Location.NorthEurope
36 | add_resource vhub
37 | }
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/content/links/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Links"
3 | date: 2020-11-02
4 | weight: 8
5 | ---
6 |
7 | In this page, you can find blog posts, videos and tweets on Farmer that will give you a better sense of the scenery and the dialogue around it.
8 |
9 |
10 | #### Blog Posts
11 | * [Compositional IT articles on Farmer](https://www.compositional-it.com/news-blog/tag/farmer/)
12 | * [Azure SQL Database deployment with Farmer, DbUp and GitHub Actions](https://www.azurefromthetrenches.com/azure-sql-database-deployment-with-farmer-dbup-and-github-actions/)
13 | * [Introduction to Farmer - IaC with Azure](https://www.svenmalvik.com/azure-first-farmer-project/)
14 | * [Farmer: Simpler ARM deployments with Octopus Deploy](https://octopus.com/blog/farmer-and-octopus-deploy)
15 |
16 | #### Videos
17 | * [Learn how to deploy complete .NET Web Apps to Azure in less than 5 minutes!](https://www.youtube.com/watch?v=5nRZwxMQUFE&feature=emb_title)
18 | * [Working with raw JSON ARM resources with Farmer](https://www.youtube.com/watch?v=a8pWqGqPKGg)
19 | * [Authoring ARM templates the easy way with Farmer](https://www.youtube.com/watch?v=w-tgwwAR8_Y)
20 | * [Making Infrastructure as Code Easier in Azure](https://www.youtube.com/watch?v=8E63s2QlbhA)
21 | * [Introduction to Farmer and Stepping up the game with ARM templates](https://www.youtube.com/watch?v=k0puV8XJ59E)
--------------------------------------------------------------------------------
/src/Farmer/Arm/B2cTenant.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.B2cTenant
3 |
4 | open Farmer
5 |
6 | let b2cTenant =
7 | ResourceType("Microsoft.AzureActiveDirectory/b2cDirectories", "2021-04-01")
8 |
9 | type B2cDomainName =
10 | | B2cDomainName of string
11 |
12 | static member internal Empty = B2cDomainName ""
13 |
14 | member this.AsResourceName =
15 | match this with
16 | | B2cDomainName name -> ResourceName name
17 |
18 | type B2cTenant = {
19 | Name: B2cDomainName
20 | DisplayName: string
21 | DataResidency: Location
22 | CountryCode: string
23 | Tags: Map
24 | Sku: B2cTenant.Sku
25 | } with
26 |
27 | interface IArmResource with
28 | member this.ResourceId = accounts.resourceId this.Name.AsResourceName
29 |
30 | member this.JsonModel = {|
31 | b2cTenant.Create(this.Name.AsResourceName, this.DataResidency, tags = this.Tags) with
32 | sku = {|
33 | name = string this.Sku
34 | tier = "A0"
35 | |}
36 | properties = {|
37 | createTenantProperties = {|
38 | countryCode = this.CountryCode
39 | displayName = this.DisplayName
40 | |}
41 | |}
42 | |}
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/container-registry.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Container Registry"
3 | date: 2020-04-30T19:10:46+02:00
4 | chapter: false
5 | weight: 3
6 | ---
7 |
8 | #### Overview
9 | The Container Registry builder is used to create Azure Container Registry (ACR) instances.
10 |
11 | * Container Registry (`Microsoft.ContainerRegistry/registries`)
12 |
13 | #### Builder Keywords
14 | | Keyword | Purpose |
15 | |-|-|
16 | | name | Sets the name of the Container Registry instance. |
17 | | sku | Sets the SKU of the instance. Defaults to Basic. |
18 | | enable_admin_user | The value that indicates whether the admin user is enabled. |
19 |
20 | #### Configuration Members
21 |
22 | | Member | Purpose |
23 | |-|-|
24 | | Password | Gets the ARM expression path to the first admin password of this container registry if the admin user was enabled. |
25 | | Password2 | Gets the ARM expression path to the second admin password of this container registry if the admin user was enabled. |
26 | | Username | Gets the ARM expression path to the admin username of this container registry if the admin user was enabled. |
27 |
28 | #### Example
29 | ```fsharp
30 | open Farmer
31 | open Farmer.Builders
32 |
33 | let myRegistry = containerRegistry {
34 | name "myRegistry"
35 | sku ContainerRegistry.Basic
36 | enable_admin_user
37 | }
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/redis.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Redis Cache"
3 | date: 2020-02-23T20:00:00+01:00
4 | chapter: false
5 | weight: 17
6 | ---
7 |
8 | #### Overview
9 | The Redis builder creates managed Redis Cache accounts.
10 |
11 | * Redis (`Microsoft.Cache/redis`)
12 |
13 | #### Builder Keywords
14 | | Keyword | Purpose |
15 | |-|-|
16 | | name | Sets the name of the Redis cache instance. |
17 | | sku | Sets the sku of the Redis cache instance. |
18 | | capacity | Sets the capacity level of the Redis cache instance, which should be between 1-6 - see [here](https://azure.microsoft.com/en-gb/pricing/details/cache/). |
19 | | enable_non_ssl_port | Enabled access to the cache over the non-SSL port. |
20 | | setting | Allows you to set a Redis-cache specific setting at deployment-time |
21 |
22 | #### Configuration Members
23 | | Member | Purpose |
24 | |-|-|
25 | | Key | Gets an ARM expression for the primary key of the Redis cache instance. |
26 |
27 | #### Example
28 |
29 | ```fsharp
30 | open Farmer
31 | open Farmer.Builders.Redis
32 |
33 | let myCache = redis {
34 | name "myredis"
35 | sku Redis.Standard
36 | capacity 1
37 | enable_non_ssl_port
38 | setting "maxclients" 256
39 | setting "maxmemory-reserved" 2
40 | setting "maxfragmentationmemory-reserved" 12
41 | setting "maxmemory-delta" 2
42 | }
43 | ```
44 |
--------------------------------------------------------------------------------
/docs/content/contributing/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Contributing"
3 | date: 2020-06-15T03:57:42+02:00
4 | draft: false
5 | chapter: false
6 | weight: 8
7 | ---
8 |
9 | Thanks for thinking about contributing! Azure is a giant beast and help supporting more use-cases is always appreciated. To make it easier to contribute, we put together this little guide. Please take a few minutes to read through before starting work on a pull request (PR) to Farmer.
10 |
11 | ### The process (don't worry... this is not waterfall)
12 | 1. Open an issue, or comment on an existing open issue covering the resource you would like to work on. Basically, a PR from you should not come as a surprise.
13 | 1. Implement the 20% of features that cover 80% of the use cases.
14 | 1. PR against the `master` branch from your *fork*.
15 | 1. Add/update tests as required.
16 | 1. Create a new **.md* file with the name of your resource in the folder **/content/api-overview/resources/**. Eg. **container-registry.md**
17 | 1. Add a description, keywords, and an example to the docs page.
18 | 1. PRs need to pass build/test against both Linux & Windows build, and a review, before being merged in.
19 |
20 | ### TODO
21 | There's still more to document!
22 |
23 | * Validation best practices
24 | * Multiple resource builders
25 | * Linking resources (one-to-many relationships)
26 | * Post-deploy tasks
--------------------------------------------------------------------------------
/src/Tests/Types.fs:
--------------------------------------------------------------------------------
1 | module Types
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.Builders
6 | open System
7 | open Newtonsoft.Json.Linq
8 |
9 | let tests =
10 | testList "Type Tests" [
11 | test "Creates deterministic GUID correctly" {
12 | let actual = DeterministicGuid.create "hello"
13 | Expect.equal (Guid.Parse "4fbe461c-3438-55c4-941e-d1c2013210c5") actual "Incorrect GUID"
14 | }
15 | test "Location.ResourceGroup emits correct ARM expression" {
16 | Expect.equal
17 | Location.ResourceGroup.ArmValue
18 | "[resourceGroup().location]"
19 | "Incorrect expression emitted for Location.ResourceGroup"
20 | }
21 | test "Default location for 'arm' builder uses resourceGroup location" {
22 | let deployment =
23 | let dummyResource = storageAccount { name "mystorageaccount74785" }
24 | arm { add_resource dummyResource }
25 |
26 | let jobj = deployment.Template |> Writer.toJson |> JToken.Parse
27 |
28 | Expect.equal
29 | (jobj.SelectToken "resources[?(@.name=='mystorageaccount74785')].location")
30 | (JValue "[resourceGroup().location]")
31 | "Default location on resource should be resource group."
32 | }
33 | ]
--------------------------------------------------------------------------------
/src/Tests/CommunicationServices.fs:
--------------------------------------------------------------------------------
1 | module CommunicationServices
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.Arm
6 | open Farmer.Builders
7 |
8 | let tests =
9 | testList "Communication Services" [
10 | test "Basic test" {
11 | let tags = [ "a", "1"; "b", "2" ]
12 |
13 | let swa = communicationService {
14 | name "test"
15 | add_tags tags
16 | data_location DataLocation.Australia
17 | }
18 |
19 | let baseArm = (swa :> IBuilder).BuildResources(Location.WestEurope).[0]
20 | let bsArm = baseArm :?> CommunicationService
21 | Expect.equal bsArm.Name (ResourceName "test") "Name"
22 | Expect.equal bsArm.DataLocation DataLocation.Australia "Data Location"
23 | Expect.equal bsArm.Tags (Map tags) "Tags"
24 | }
25 |
26 | test "Default options test" {
27 | let swa = communicationService { name "test" }
28 |
29 | let baseArm = (swa :> IBuilder).BuildResources(Location.WestEurope).[0]
30 | let bsArm = baseArm :?> CommunicationService
31 | Expect.equal bsArm.Name (ResourceName "test") "Name"
32 | Expect.equal bsArm.DataLocation DataLocation.UnitedStates "Data Location"
33 | Expect.isEmpty bsArm.Tags "Tags"
34 | }
35 | ]
--------------------------------------------------------------------------------
/docs/content/api-overview/basic-types.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Basic Types"
3 | date: 2022-05-25T13:32:12+01:00
4 | draft: false
5 | weight: 3
6 | ---
7 | Farmer often uses references to link resouces, when this is not possible, Farmer uses certain types to define links.
8 |
9 | #### ResourceName
10 |
11 | A ResourceName represents a name of an ARM resource.
12 |
13 |
14 | ```fsharp
15 | ResourceName "myapp"
16 | ```
17 |
18 | #### ResourceId
19 |
20 | A ResourceId identifies an ARM resource.
21 |
22 | This is used when you want to link unmanaged resources to a deployment or have multiple deployment parts.
23 |
24 | A ResourceId can be created with the resourceId member of a ResourceType:
25 |
26 | ```fsharp
27 | let subnet = Arm.Network.virtualNetworks.resourceId(ResourceName "")
28 | ```
29 |
30 | or directly as a ResourceId:
31 |
32 | ```fsharp
33 | let mySubnet = {
34 | Type = Arm.Network.subnets
35 | Name = ResourceName "my-vnet"
36 | ResourceGroup = Some("myResourceGroup")
37 | Subscription = None
38 | Segments = [ResourceName "mySubnet"]
39 | }
40 | ```
41 |
42 | These resources can be used i.e. in referencing a `web app { }` to a subnet.
43 |
44 | ```fsharp
45 | let webApp = webApp {
46 | name "myWebApp"
47 | service_plan_name "myServicePlan"
48 | sku WebApp.Sku.B1
49 | link_to_unmanaged_vnet mySubnet
50 | }
51 | ```
52 |
--------------------------------------------------------------------------------
/docs/content/api-overview/outputs.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Outputs"
3 | date: 2020-08-22T09:13:36+01:00
4 | draft: false
5 | weight: 3
6 | ---
7 | ARM templates also support the notion of *[outputs](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-outputs)*. Outputs can be used to provide your Farmer applications with values which were generated during the deployment process, to be used further downstream.
8 |
9 | For example, you may wish to prime an Azure storage account with data post-creation. In this case, one way is to return back out the connection string of the storage account and use that to connect and upload your data.
10 |
11 | #### Creating and Consuming outputs
12 | Outputs are applied onto the `arm { }` builder using the [output keyword](../resources/arm/#builder-keywords).
13 |
14 | ```fsharp
15 | let myStorage = storageAccount {
16 | name "sampleaccount"
17 | }
18 |
19 | let template = arm {
20 | add_resource myStorage
21 | output "storage_key" myStorage.Key
22 | }
23 |
24 | let outputs = template |> Deploy.execute "my-resource-group" []
25 |
26 | let connectionString = outputs.["storage_key"]
27 | ```
28 |
29 | Outputs are returned back from the deployment as a simple `Map`.
30 |
31 | Any [ARM expression](../expressions) can be returned as an output, and you can create as many outputs as you wish.
32 |
--------------------------------------------------------------------------------
/MAINTAINERS.md:
--------------------------------------------------------------------------------
1 | Maintainers Guide
2 | =================
3 |
4 | This guide is for _maintainers_ who have commit access to the repository and can merge PR's and create release tags.
5 |
6 | Release Process
7 | ---------------
8 |
9 | The release process starts with drafting a new release in GitHub and results in a nuget package published to the public feed.
10 |
11 | 1. Update the `` tag in the [Farmer.fsproj](src/Farmer/Farmer.fsproj) file with the version number and commit it to master.
12 | 2. Go to [releases](https://github.com/CompositionalIT/farmer/releases) and click "Draft a new Release".
13 | 3. Under "Choose a tag", enter the version number for the new package to release to create a tag for the version when it is published. Creating this tag starts the Azure DevOps Pipeline.
14 | 4. In "Release Title", name it for the version number as well. This makes it easier to correlate a particular release with the nuget package when viewing the list of releases.
15 | 5. In "Describe this release", include the bullet points from the release notes for all the features added in this release. Viewing the "raw" [RELEASE_NOTES.md](https://raw.githubusercontent.com/CompositionalIT/farmer/master/RELEASE_NOTES.md) makes it easier to copy these for the description.
16 | 6. Click "Publish Release".
17 | 7. Go to the Azure DevOps pipeline and wait for the approval stage. If everything looks good, approve to publish the package to nuget.
18 |
--------------------------------------------------------------------------------
/src/Tests/EventHub.fs:
--------------------------------------------------------------------------------
1 | module EventHub
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.Builders
6 | open Farmer.EventHub
7 |
8 | let tests =
9 | testList "EventHub" [
10 | test "Gets key on a Hub correctly" {
11 | let hub = eventHub { name "foo" }
12 |
13 | Expect.equal
14 | hub.DefaultKey.Owner.Value.ArmExpression.Value
15 | "resourceId('Microsoft.EventHub/namespaces/eventhubs', 'foo')"
16 | "Incorrect owner"
17 |
18 | Expect.equal
19 | hub.DefaultKey.Value
20 | "listkeys(resourceId('Microsoft.EventHub/namespaces/AuthorizationRules', 'foo-ns', 'RootManageSharedAccessKey'), '2017-04-01').primaryConnectionString"
21 | "Incorrect key"
22 | }
23 | test "Does not explicitly create default consumer group" {
24 | let hub = eventHub {
25 | name "test-event-hub"
26 | // When using Basic tier, attempting to explicitly create a "$Default" consumer group
27 | // will give an error because Basic doesn't support creating consumer groups.
28 | sku EventHubSku.Basic
29 | }
30 |
31 | let defaultResourceName = ResourceName "$Default"
32 | let defaultConsumerGroupExists = hub.ConsumerGroups.Contains defaultResourceName
33 | Expect.isFalse defaultConsumerGroupExists "Created a default consumer group"
34 | }
35 | ]
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/virtual-wan.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Virtual WAN"
3 | date: 2021-05-03T11:22:17-05:00
4 | chapter: false
5 | weight: 21
6 | ---
7 |
8 | #### Overview
9 |
10 | The Virtual WAN builder (`vwan`) is used to create Azure Virtual WAN instances.
11 |
12 | - Virtual WAN (`Microsoft.Network/virtualWans`)
13 |
14 | #### Builder Keywords
15 |
16 | | Resource | Keyword | Purpose |
17 | | -------------- | -------------------- | -----------------------------------------------------------------------|
18 | | vwan | name | Sets the name of the virtual wan |
19 | | vwan | standard_vwan | Sets the virtual wan type to "standard" instead of the default "basic" |
20 | | vwan | allow_branch_to_branch_traffic | Specifies branch to branch traffic is allowed |
21 | | vwan | disable_vpn_encryption | Specifies Vpn encryption is disabled |
22 | | vwan | office_365_local_breakout_category | Sets the office local breakout category |
23 |
24 |
25 | ### Example
26 |
27 | ```fsharp
28 | open Farmer
29 | open Farmer.Builders
30 |
31 | let myVwan = vwan {
32 | name "my-vwan"
33 | disable_vpn_encryption
34 | allow_branch_to_branch_traffic
35 | office_365_local_breakout_category Office365LocalBreakoutCategory.None
36 | standard_vwan
37 | }
38 | let deployment = arm {
39 | location Location.NorthEurope
40 | add_resource myVwan
41 | }
42 | ```
--------------------------------------------------------------------------------
/samples/scripts/sqlserver.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Sql
6 |
7 | let myDatabases = sqlServer {
8 | name "isaac_super_server"
9 | admin_username "admin_username"
10 | enable_azure_firewall
11 |
12 | add_databases [
13 | sqlDb { name "poolDb1" }
14 | sqlDb { name "poolDb2" }
15 | sqlDb {
16 | name "dtuDb"
17 | sku Basic
18 | }
19 | sqlDb {
20 | name "memoryDb"
21 | sku M_8
22 | }
23 | sqlDb {
24 | name "cpuDb"
25 | sku Fsv2_8
26 | }
27 | sqlDb {
28 | name "businessCriticalDb"
29 | sku (BusinessCritical Gen5_2)
30 | }
31 | sqlDb {
32 | name "hyperscaleDb"
33 | sku (Hyperscale Gen5_2)
34 | }
35 | sqlDb {
36 | name "generalPurposeDb"
37 | sku (GeneralPurpose Gen5_8)
38 | db_size (1024 * 128)
39 | hybrid_benefit
40 | }
41 | sqlDb {
42 | name "serverless4to8cpu"
43 | sku (GeneralPurpose(S_Gen5(4, 8)))
44 | }
45 | sqlDb {
46 | name "serverlessHalfCore"
47 | sku (GeneralPurpose(S_Gen5(0.5, 2.0)))
48 | }
49 | ]
50 | }
51 |
52 | let template = arm {
53 | location Location.NorthEurope
54 | add_resource myDatabases
55 | }
56 |
57 | template |> Writer.quickWrite "sql-example"
--------------------------------------------------------------------------------
/docs/content/contributing/outputs-and-expressions.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Outputs and ARM Expressions"
3 | date: 2020-06-19T23:50:54+02:00
4 | draft: false
5 | weight: 2
6 | ---
7 |
8 | Outputs can be created in Farmer for any [ARM Expression](../../api-overview/expressions), Resource Name or any optional string. ARM Expressions are most useful in this case for referring to values that only exist at *deployment time*, such as connection strings.
9 |
10 | ###
11 |
12 | ### Creating ARM Expressions
13 | Farmer ARM expressions are in reality just wrapped strings, and are easy to create. For example, the code to create a Storage Key property is similar to this:
14 |
15 | ```fsharp
16 | let buildKey accountName : ArmExpression =
17 | // Create the raw string of the expression
18 | let rawValue =
19 | $"concat('DefaultEndpointsProtocol=https;AccountName={accountName};AccountKey=', listKeys('{accountName}', '2017-10-01').keys[0].value)"
20 |
21 | // Wrap the raw value in an ARM Expression and return it
22 | ArmExpression.create rawValue
23 | ```
24 |
25 | Notice that you do *not* wrap the expression in square brackets [ ]; Farmer will do this when writing out the ARM template.
26 |
27 | ### Extracting the value of an ARM Expression
28 | ARM expressions also have the following members on them:
29 | * `Map` - standard map
30 | * `Bind` - standard bind
31 | * `Value` - Returns the raw string value
32 | * `Eval` - Returns the string as a formatted ARM expression i.e. surround in `[]`
33 |
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/signalr.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "SignalR"
3 | date: 2020-06-01T11:13:00+01:00
4 | chapter: false
5 | weight: 18
6 | ---
7 |
8 | #### Overview
9 | The SignalR builder creates SignalR services.
10 |
11 | * SignalR Service (`Microsoft.SignalRService/signalR`)
12 |
13 | #### Builder Keywords
14 | | Keyword | Purpose |
15 | |-|-|
16 | | name | Sets the name of the SignalR service. |
17 | | sku | Sets the sku of the SignalR service. |
18 | | capacity | Sets the capacity of the SignalR service. (optional) |
19 | | service_mode | Sets the service mode of the SignalR service. (optional) |
20 | | allowed_origins | Sets the allowed origins of the SignalR service. (optional) |
21 |
22 | #### Configuration Members
23 |
24 | | Member | Purpose |
25 | |-|-|
26 | | Key | Returns an ARM expression to retrieve the primary Key of the service. Useful for e.g. supplying the connection string to another resource e.g. KeyVault or an app setting in the App Service. |
27 | | ConnectionString | Returns an ARM expression to retrieve the primary Connection String of the service. Useful for e.g. supplying the connection string to another resource e.g. KeyVault or an app setting in the App Service. |
28 |
29 | #### Example
30 |
31 | ```fsharp
32 | open Farmer
33 | open Farmer.Builders
34 |
35 | let mySignalR = signalR {
36 | name "mysignalr"
37 | sku SignalR.Standard
38 | capacity 10
39 | service_mode ServiceMode.Default
40 | allowed_origins [ "https://github.com" ]
41 | }
42 | ```
43 |
--------------------------------------------------------------------------------
/src/Farmer/Arm/OperationsManagement.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.OperationsManagement
3 |
4 | open Farmer
5 |
6 | let oms =
7 | ResourceType("Microsoft.OperationsManagement/solutions", "2015-11-01-preview")
8 |
9 | type OMS = {
10 | Name: ResourceName
11 | Location: Location
12 | Plan: {|
13 | Name: string
14 | Product: string
15 | Publisher: string
16 | |}
17 | Properties: {|
18 | ContainedResources: ResourceId list
19 | ReferencedResources: ResourceId list
20 | WorkspaceResourceId: ResourceId
21 | |}
22 | Tags: Map
23 | } with
24 |
25 | interface IArmResource with
26 | member this.ResourceId = oms.resourceId this.Name
27 |
28 | member this.JsonModel = {|
29 | oms.Create(this.Name, this.Location, [ this.Properties.WorkspaceResourceId ], tags = this.Tags) with
30 | plan = {|
31 | name = this.Plan.Name
32 | publisher = this.Plan.Publisher
33 | product = this.Plan.Product
34 | promotionCode = ""
35 | |}
36 | properties = {|
37 | workspaceResourceId = this.Properties.WorkspaceResourceId.Eval()
38 | containedResources = this.Properties.ContainedResources |> List.map (fun cr -> cr.Eval())
39 | referencedResources = this.Properties.ReferencedResources |> List.map (fun rr -> rr.Eval())
40 | |}
41 | |}
--------------------------------------------------------------------------------
/src/Tests/AvailabilityTests.fs:
--------------------------------------------------------------------------------
1 | module AppInsightsAvailability
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.Builders
6 |
7 | let tests =
8 | testList "AvailabilityTests" [
9 |
10 | test "Create an availability test" {
11 | let ai = appInsights { name "ai" }
12 |
13 | let availabilityTest = availabilityTest {
14 | name "avTest"
15 | link_to_app_insights ai
16 | timeout 60
17 | frequency 800
18 | locations [ AvailabilityTest.TestSiteLocation.CentralUS ]
19 | web_test ("https://google.com" |> System.Uri |> AvailabilityTest.WebsiteUrl)
20 | }
21 |
22 | let template = arm { add_resources [ availabilityTest; ai ] }
23 | let jsn = template.Template |> Writer.toJson
24 | let jobj = jsn |> Newtonsoft.Json.Linq.JObject.Parse
25 |
26 | let hasWebTest =
27 | jobj.SelectToken("resources[?(@.name=='avTest')].properties.Configuration.WebTest")
28 |
29 | Expect.isNotNull hasWebTest "WebTest context missing"
30 |
31 | let availabilityLocation =
32 | jobj.SelectToken("resources[?(@.name=='avTest')].properties.Locations[0].Id")
33 |
34 | Expect.equal (availabilityLocation.ToString()) "us-fl-mia-edge" "WebTest location incorrect"
35 | let dependsAi = jobj.SelectToken("resources[?(@.name=='avTest')].dependsOn")
36 | Expect.isNotNull dependsAi "AppInsights dependency missing"
37 | }
38 | ]
--------------------------------------------------------------------------------
/src/Farmer/Arm/Search.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.Search
3 |
4 | open Farmer
5 | open Farmer.Search
6 |
7 | let searchServices = ResourceType("Microsoft.Search/searchServices", "2015-08-19")
8 |
9 | type SearchService = {
10 | Name: ResourceName
11 | Location: Location
12 | Sku: Sku
13 | ReplicaCount: int
14 | PartitionCount: int
15 | Tags: Map
16 | } with
17 |
18 | member this.HostingMode =
19 | match this.Sku with
20 | | Standard3 HighDensity -> "highDensity"
21 | | _ -> "default"
22 |
23 | interface IArmResource with
24 | member this.ResourceId = searchServices.resourceId this.Name
25 |
26 | member this.JsonModel = {|
27 | searchServices.Create(this.Name, this.Location, tags = this.Tags) with
28 | sku = {|
29 | name =
30 | match this.Sku with
31 | | Free -> "free"
32 | | Basic -> "basic"
33 | | Standard -> "standard"
34 | | Standard2 -> "standard2"
35 | | Standard3 _ -> "standard3"
36 | | StorageOptimisedL1 -> "storage_optimized_l1"
37 | | StorageOptimisedL2 -> "storage_optimized_l2"
38 | |}
39 | properties = {|
40 | replicaCount = this.ReplicaCount
41 | partitionCount = this.PartitionCount
42 | hostingMode = this.HostingMode
43 | |}
44 | |}
--------------------------------------------------------------------------------
/samples/scripts/vnet.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.Network
6 |
7 | let serviceEndpointPolicy =
8 | Farmer.Arm.Network.serviceEndpointPolicies.resourceId "svc-endpt-policy"
9 |
10 |
11 | let privateNet = vnet {
12 | name "my-vnet"
13 |
14 | build_address_spaces [
15 | addressSpace {
16 | space "10.28.0.0/16"
17 | build_subnet "services" 24
18 | build_subnet "corporate-west" 18
19 | build_subnet "corporate-east" 18
20 | build_subnet "GatewaySubnet" 29
21 | build_subnet_delegated "containers" 27 [ SubnetDelegationService.ContainerGroups ]
22 |
23 | build_subnet_service_endpoints "can-use-servicebus" 28 [
24 | EndpointServiceType.ServiceBus, [ Location.EastUS ]
25 | ]
26 |
27 | build_subnet_service_endpoint_policies "can-use-storage" 28 [
28 | EndpointServiceType.Storage, [ Location.EastUS ]
29 | ] [ serviceEndpointPolicy ]
30 |
31 | subnets [
32 | buildSubnet "more-services" 24
33 | buildSubnetDelegations "more-containers" 27 [ SubnetDelegationService.ContainerGroups ]
34 | ]
35 | }
36 | addressSpace {
37 | space "10.30.0.0/16"
38 | subnets [ buildSubnet "stuff" 23; buildSubnet "more-stuff" 28 ]
39 | }
40 | ]
41 | }
42 |
43 | let deployment = arm {
44 | location Location.EastUS
45 | add_resource privateNet
46 | }
47 |
48 | deployment |> Writer.quickWrite "vnet-sample"
--------------------------------------------------------------------------------
/src/Farmer/Arm/SignalR.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.SignalRService
3 |
4 | open Farmer
5 | open Farmer.SignalR
6 |
7 | let signalR = ResourceType("Microsoft.SignalRService/signalR", "2018-10-01")
8 |
9 | type SignalR = {
10 | Name: ResourceName
11 | Location: Location
12 | Sku: Sku
13 | Capacity: int option
14 | AllowedOrigins: string list
15 | ServiceMode: ServiceMode
16 | Tags: Map
17 | } with
18 |
19 | interface IArmResource with
20 | member this.ResourceId = signalR.resourceId this.Name
21 |
22 | member this.JsonModel = {|
23 | signalR.Create(this.Name, this.Location, tags = this.Tags) with
24 | sku = {|
25 | name =
26 | match this.Sku with
27 | | Free -> "Free_F1"
28 | | Standard -> "Standard_S1"
29 | capacity =
30 | match this.Capacity with
31 | | Some c -> c.ToString()
32 | | None -> null
33 | |}
34 | properties = {|
35 | cors =
36 | match this.AllowedOrigins with
37 | | [] -> null
38 | | aos -> box {| allowedOrigins = aos |}
39 | features = [
40 | {|
41 | flag = "ServiceMode"
42 | value = this.ServiceMode.ToString()
43 | |}
44 | ]
45 | |}
46 | |}
--------------------------------------------------------------------------------
/src/Farmer/Builders/Builders.Maps.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Builders.Maps
3 |
4 | open Farmer
5 | open Farmer.Maps
6 | open Farmer.Helpers
7 | open Farmer.Arm.Maps
8 |
9 | type MapsConfig = {
10 | Name: ResourceName
11 | Sku: Sku
12 | Tags: Map
13 | } with
14 |
15 | interface IBuilder with
16 | member this.ResourceId = accounts.resourceId this.Name
17 |
18 | member this.BuildResources _ = [
19 | {
20 | Name = this.Name
21 | Location = Location "global"
22 | Sku = this.Sku
23 | Tags = this.Tags
24 | }
25 | ]
26 |
27 | type MapsBuilder() =
28 | member _.Yield _ = {
29 | Name = ResourceName.Empty
30 | Sku = S0
31 | Tags = Map.empty
32 | }
33 |
34 | member _.Run(state: MapsConfig) = {
35 | state with
36 | Name = state.Name |> sanitiseMaps |> ResourceName
37 | }
38 |
39 | /// Sets the name of the Azure Maps instance.
40 | []
41 | member _.Name(state: MapsConfig, name) = { state with Name = name }
42 |
43 | member this.Name(state: MapsConfig, name) = this.Name(state, ResourceName name)
44 |
45 | /// Sets the SKU of the Azure Maps instance.
46 | []
47 | member _.Sku(state: MapsConfig, sku) = { state with Sku = sku }
48 |
49 | interface ITaggable with
50 | member _.Add state tags = {
51 | state with
52 | Tags = state.Tags |> Map.merge tags
53 | }
54 |
55 | let maps = MapsBuilder()
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Farmer Docs
2 |
3 | ## About the Documentation Platform
4 |
5 | Farmer's docs use [Hugo](https://gohugo.io/) and the
6 | [hugo-theme-learn theme](https://github.com/compositionalit/hugo-theme-learn).
7 |
8 | ## To build these docs locally
9 |
10 | * Install [Go language](https://golang.org/) support and
11 | [Hugo](https://gohugo.io/) (if on Windows, we recommend using
12 | [Chocolatey](https://chocolatey.org/) and running `choco install golang hugo`)
13 |
14 | *Note*: there is currently a problem with the newest version of Hugo and the
15 | theme, so use version 0.68.3 of Hugo, otherwise you'll get a compilation error
16 | * The theme is in a sub-module, so you'll also want to run
17 | `git submodule update --init` and then `cd docs/themes` followed by
18 | `git clone https://github.com/compositionalit/hugo-theme-learn.git`
19 | * To build, run `hugo --minify` from the `docs` folder.
20 | To serve a local copy, run `hugo server`.
21 |
22 | ## Publishing these Docs
23 |
24 | These docs use [GitHub Actions](https://github.com/features/actions) and the
25 | [Actions-hugo](https://github.com/peaceiris/actions-hugo) tooling to publish
26 | the contents to [GitHub pages](https://pages.github.com/)
27 |
28 | How it works:
29 |
30 | * A change is committed to the `master` branch (say, when a PR is merged).
31 | * The GitHub Actions workflow begins.
32 | * The action runs hugo against the `docs` folder, and then publishes the
33 | `public` folder output to the `gh-pages` branch.
34 | * The `gh-pages` branch is served by GitHub Pages at
35 | https://compositionalit.github.io/farmer.
36 |
--------------------------------------------------------------------------------
/src/Farmer/Builders/Builders.DataLake.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Builders.DataLake
3 |
4 | open Farmer
5 | open Farmer.DataLake
6 | open Farmer.Arm.DataLakeStore
7 |
8 | type DataLakeConfig = {
9 | Name: ResourceName
10 | EncryptionState: FeatureFlag
11 | Sku: Sku
12 | Tags: Map
13 | } with
14 |
15 | interface IBuilder with
16 | member this.ResourceId = accounts.resourceId this.Name
17 |
18 | member this.BuildResources location = [
19 | {
20 | Name = this.Name
21 | Location = location
22 | EncryptionState = this.EncryptionState
23 | Sku = this.Sku
24 | Tags = this.Tags
25 | }
26 | ]
27 |
28 | type DataLakeBuilder() =
29 | member _.Yield _ = {
30 | Name = ResourceName ""
31 | EncryptionState = Disabled
32 | Sku = Sku.Consumption
33 | Tags = Map.empty
34 | }
35 |
36 | /// Sets the name of the data lake.
37 | []
38 | member _.Name(state: DataLakeConfig, name) = { state with Name = ResourceName name }
39 |
40 | []
41 | member _.EncryptionState(state: DataLakeConfig) = { state with EncryptionState = Enabled }
42 |
43 | []
44 | member _.Sku(state: DataLakeConfig, sku) = { state with Sku = sku }
45 |
46 | interface ITaggable with
47 | member _.Add state tags = {
48 | state with
49 | Tags = state.Tags |> Map.merge tags
50 | }
51 |
52 | let dataLake = DataLakeBuilder()
--------------------------------------------------------------------------------
/docs/content/contributing/adding-resources/5-unit-testing.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "5. Unit Testing"
3 | draft: false
4 | chapter: false
5 | weight: 5
6 | ---
7 |
8 | Usually I would be pro writing the tests before you implement all this but it is important to get a feel for the moving parts. At this point you may want to write some tests so you can iterate quickly on getting the structure of your ARM template correct.
9 |
10 | The tests you will find in the project are black-box style tests that focus on the input of a resource and the output of the ARM template. If you want to create tests for your mapping functions that is fine but remember between the strong type system and making it difficult to have `null` values, those kind of tests seldom yield much benefit in F#.
11 |
12 | Of course, unit tests can only tell you so much when dealing with something as complex as Azure. Create a **fsx** file to run to check that your resource is deploying as expected.
13 |
14 | ```fsharp
15 | // container-registry.fsx
16 | #r "Newtonsoft.Json.dll"
17 | #r @"../Farmer/bin/Debug/netstandard2.0/Farmer.dll"
18 |
19 | open Farmer
20 | open Farmer.Resources.ContainerRegistry
21 |
22 | let myRegistry = containerRegistry {
23 | name "devonRegistry"
24 | sku ContainerRegistrySku.Basic
25 | enable_admin_user
26 | }
27 |
28 | let deployment = arm {
29 | location NorthEurope
30 | add_resource myRegistry
31 | output "registry" myRegistry.Name
32 | output "loginServer" myRegistry.LoginServer
33 | }
34 |
35 | deployment
36 | |> Deploy.execute "FarmerTest" Deploy.NoParameters
37 | |> printfn "%A"
38 | ```
39 |
40 | Create a Resource Group to run it, here I called it "FarmerTest".
41 |
42 | Run `dotnet fsi container-registry.fsx`
43 |
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/logic-apps.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Logic Apps"
3 | date: 2022-04-27T00:55:30+02:00
4 | chapter: false
5 | weight: 12
6 | ---
7 |
8 | #### Overview
9 | The Logic App builder is used to create Azure Logic App Workflows.
10 |
11 | * Workflows (`Microsoft.Logic/workflows`)
12 |
13 | #### Builder keywords
14 | | Keyword | Purpose |
15 | |-|-|
16 | | name | Sets the name of the workflow. |
17 | | definition | Sets the file path (via `FileDefinition path`) or the definition directly (via `ValueDefinition value`) |
18 | | add_tags | Adds tags to the script runtime resource. |
19 | | add_tag | Adds a tag to the script runtime resource. |
20 |
21 | #### Example
22 |
23 | ```fsharp
24 | open Farmer
25 | open Farmer.Builders
26 |
27 | let emptyLogicApp =
28 | """
29 | {
30 | "definition": {
31 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
32 | "actions": {},
33 | "contentVersion": "1.0.0.0",
34 | "outputs": {},
35 | "parameters": {},
36 | "triggers": {}
37 | },
38 | "parameters": {}
39 | }
40 | """
41 |
42 | let myValueLogicApp = logicApp {
43 | name "value-test-logic-app"
44 | definition (ValueDefinition emptyLogicApp)
45 | add_tags [("created-by", "farmer")]
46 | }
47 |
48 | let filepath = "./logicAppDefinition.json"
49 |
50 | let myFileLogicApp = logicApp {
51 | name "file-test-logic-app"
52 | definition (FileDefinition filepath)
53 | add_tags [("created-by", "farmer")]
54 | }
55 |
56 | let deployment = arm {
57 | location Location.NorthCentralUS
58 | add_resource myValueLogicApp
59 | add_resource myFileLogicApp
60 | }
61 | ```
--------------------------------------------------------------------------------
/samples/scripts/vm-delete-option.fsx:
--------------------------------------------------------------------------------
1 | // This sample demonstrates how to set delete options for VM resources
2 | // When set to Delete, the associated resources will be automatically
3 | // removed when the VM is deleted from Azure.
4 |
5 | open Farmer
6 | open Farmer.Builders
7 |
8 | // Example 1: Using the convenience method to delete all attached resources
9 | let myVmSimple = vm {
10 | name "my-vm-simple"
11 | username "azureuser"
12 | vm_size Vm.Standard_A2
13 | operating_system Vm.UbuntuServer_2204LTS
14 | os_disk 128 Vm.StandardSSD_LRS
15 | add_ssd_disk 256
16 |
17 | // Convenience method - deletes all attached resources when VM is deleted
18 | delete_attached
19 | }
20 |
21 | // Example 2: Setting delete options individually for fine-grained control
22 | let myVmDetailed = vm {
23 | name "my-vm-detailed"
24 | username "azureuser"
25 | vm_size Vm.Standard_A2
26 | operating_system Vm.UbuntuServer_2204LTS
27 | os_disk 128 Vm.StandardSSD_LRS
28 | add_ssd_disk 256
29 |
30 | // Set delete option to automatically remove disks when VM is deleted
31 | disk_delete_option Vm.DeleteOption.Delete
32 |
33 | // Set delete option to automatically remove NIC when VM is deleted
34 | nic_delete_option Vm.DeleteOption.Delete
35 |
36 | // Set delete option to automatically remove public IP when VM is deleted
37 | public_ip_delete_option Vm.DeleteOption.Delete
38 | }
39 |
40 | let deployment = arm {
41 | location Location.EastUS
42 | add_resources [ myVmSimple; myVmDetailed ]
43 | }
44 |
45 | // Generate the ARM template
46 | deployment |> Writer.quickWrite "vm-delete-option"
47 |
48 | // Or deploy directly to Azure
49 | // deployment |> Deploy.execute "my-resource-group-name" Deploy.NoParameters
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/databricks-workspace.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Databricks Workspace"
3 | date: 2021-01-31T15:43:30+00:00
4 | chapter: false
5 | weight: 4
6 | ---
7 |
8 | #### Overview
9 | The Databricks Workspace builder is used to create Azure Databricks Workspaces.
10 |
11 | * Workspace (`Microsoft.Databricks/workspaces`)
12 |
13 | #### Builder Keywords
14 | | Keyword | Purpose |
15 | |-|-|
16 | | name | Sets the name of the workspace. |
17 | | sku | Sets the pricing tier of the workspace. Defaults to Standard Tier. |
18 | | encrypt_with_key_vault | Given a key vault builder / resourceid / vault name, and the name of a key, activates the use of Key Vault for the key store. |
19 | | encrypt_with_databricks | Specifies to use DataBricks itself for key encryption. |
20 | | encrypt_with | Allows you to programmatically specify whether to use key vault or data bricks encryption. |
21 | | key_vault_key_version | Specifies the version of the key vault key to use; if this is not specified, the latest version of the key is used. |
22 | | allow_public_ip | Whether to use public IP addresses for cluster virtual machines. Defaults to Enabled. |
23 | | attach_to_vnet | Given a Resource Id / Name / VNet Config, and Public & Private Subnets, attaches the workspace to the VNet specified. |
24 | | managed_resource_group_id | Sets the name of the resource group that will be created by the workspace. Optional. |
25 |
26 | #### Example
27 | ```fsharp
28 | open Farmer
29 | open Farmer.Builders
30 |
31 | let myVault = keyVault { name "my-vault" }
32 |
33 | let myWorkspace = databricksWorkspace {
34 | name "my-databricks-workspace"
35 | sku Databricks.Sku.Standard
36 | encrypt_with_key_vault myVault "workspace-encryption-key"
37 | attach_to_vnet "databricks-vnet" "databricks-pub-snet" "databricks-priv-snet"
38 | }
39 | ```
--------------------------------------------------------------------------------
/src/Farmer/Aliases.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Aliases
3 |
4 | []
5 | module BuilderExtensions =
6 | open Farmer.Builders
7 | open Farmer.Arm.Network
8 |
9 | type IPrivateEndpoints<'TConfig> with
10 |
11 | member this.AddPrivateEndpoint(state: 'TConfig, subnetId: LinkedResource) =
12 | this.AddPrivateEndpoint(state, SubnetReference.create subnetId)
13 |
14 | member this.AddPrivateEndpoint(state: 'TConfig, subnet: SubnetConfig) =
15 | this.AddPrivateEndpoint(state, SubnetReference.create subnet)
16 |
17 | member this.AddPrivateEndpoint(state, (subnetRef: LinkedResource, epName)) =
18 | this.AddPrivateEndpoint(state, (SubnetReference.create subnetRef, epName))
19 |
20 | member this.AddPrivateEndpoint(state: 'TConfig, (vnetRef, subnetName): LinkedResource * ResourceName) =
21 | this.AddPrivateEndpoint(state, SubnetReference.create (vnetRef, subnetName))
22 |
23 | member this.AddPrivateEndpoint(state, (vnetRef, subnetName, epName): LinkedResource * ResourceName * string) =
24 | this.AddPrivateEndpoint(state, ((SubnetReference.create (vnetRef, subnetName)), epName))
25 |
26 | member this.AddPrivateEndpoint(state: 'TConfig, (vnet, subnetName): VirtualNetworkConfig * ResourceName) =
27 | this.AddPrivateEndpoint(state, SubnetReference.create (vnet, subnetName))
28 |
29 | member this.AddPrivateEndpoints(state: 'TConfig, subnetIds: LinkedResource list) =
30 | this.AddPrivateEndpoints(state, subnetIds |> List.map SubnetReference.create |> Set)
31 |
32 | member this.AddPrivateEndpoints(state: 'TConfig, subnets: SubnetConfig list) =
33 | this.AddPrivateEndpoints(state, subnets |> List.map SubnetReference.create |> Set)
34 |
35 | let arm = Farmer.Builders.ResourceGroup.DeploymentBuilder()
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/app-insights.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "App Insights"
3 | date: 2020-02-05T08:53:46+01:00
4 | weight: 1
5 | chapter: false
6 | ---
7 |
8 | #### Overview
9 | The App Insights builder is used to create Application Insights accounts. Use this if you need a standalone AI instance; if you need one for a web app, the web app will create one by default and configure the application settings automatically.
10 |
11 | * Application Insights (`Microsoft.Insights/components`)
12 |
13 | > This builder supports both "Classic" (standalone) and "Workspace Enabled" (Log Analytics-backed) instances of App Insights. See the `log_analytics_workspace` keyword to see how to create the latter type of instance.
14 |
15 | #### Builder Keywords
16 |
17 | | Keyword | Purpose |
18 | |-|-|
19 | | name | Sets the name of the App Insights instance. |
20 | | disable_ip_masking | Disable IP masking. |
21 | | sampling_percentage | Define sampling percentage (0-100) |
22 | | log_analytics_workspace | Use a Log Analytics workspace as the backing store for this AI instance. You can supply either a Farmer-generate Log Analytics`WorkspaceConfig` instance that exists in the same resource group, or a fully-qualified Resource ID path to that instance. This will also switch the AI instance over to create a "workspace enabled" AI instance. |
23 |
24 | #### Configuration Members
25 |
26 | | Member | Purpose |
27 | |-|-|
28 | | InstrumentationKey | Gets the ARM expression path to the instrumentation key of this App Insights instance. |
29 | | ConnectionString | Gets the ARM expression path to the connection string of this App Insights instance. |
30 |
31 | #### Example
32 |
33 | ```fsharp
34 | open Farmer
35 | open Farmer.Builders
36 |
37 | let ai = appInsights {
38 | name "myAI"
39 | log_analytics_workspace myWorkspace // use to activate workspace-enabled AI instances.
40 | }
41 | ```
42 |
--------------------------------------------------------------------------------
/src/Farmer/Arm/VirtualWan.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.VirtualWan
3 |
4 | open Farmer
5 |
6 | let virtualWans = ResourceType("Microsoft.Network/virtualWans", "2020-07-01")
7 |
8 | []
9 | type Office365LocalBreakoutCategory =
10 | | Optimize
11 | | OptimizeAndAllow
12 | | All
13 | | None
14 |
15 | member this.ArmValue =
16 | match this with
17 | | Optimize -> "Optimize"
18 | | OptimizeAndAllow -> "OptimizeAndAllow"
19 | | All -> "All"
20 | | None -> "None"
21 |
22 | []
23 | type VwanType =
24 | | Standard
25 | | Basic
26 |
27 | member this.ArmValue =
28 | match this with
29 | | Standard -> "Standard"
30 | | Basic -> "Basic"
31 |
32 | type VirtualWan = {
33 | Name: ResourceName
34 | Location: Location
35 | AllowBranchToBranchTraffic: bool option
36 | DisableVpnEncryption: bool option
37 | Office365LocalBreakoutCategory: Office365LocalBreakoutCategory option
38 | VwanType: VwanType
39 | } with
40 |
41 | interface IArmResource with
42 | member this.ResourceId = virtualWans.resourceId this.Name
43 |
44 | member this.JsonModel = {|
45 | virtualWans.Create(this.Name, this.Location) with
46 | properties = {|
47 | allowBranchToBranchTraffic = this.AllowBranchToBranchTraffic |> Option.defaultValue false
48 | disableVpnEncryption = this.DisableVpnEncryption |> Option.defaultValue false
49 | office365LocalBreakoutCategory =
50 | (this.Office365LocalBreakoutCategory
51 | |> Option.defaultValue Office365LocalBreakoutCategory.None)
52 | .ArmValue
53 | ``type`` = this.VwanType.ArmValue
54 | |}
55 | |}
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/loganalytics.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Log Analytics"
3 | date: 2020-10-7T19:10:46+02:00
4 | chapter: false
5 | weight: 12
6 | ---
7 |
8 | #### Overview
9 |
10 | The Log Analytics builder is used to create Work space instances.
11 |
12 | - Log Analytics (`Microsoft.OperationalInsights/workspaces`)
13 |
14 | #### Builder Keywords
15 |
16 | | Keyword | Purpose |
17 | | ---------------- | --------------------------------------------------------------- |
18 | | name | Sets the name of the log analytics instance. |
19 | | retention_period | Sets the retention period for logs in days. |
20 | | enable_ingestion | Enables ingestion network traffic. |
21 | | enable_query | Enables query network traffic. |
22 | | daily_cap | Specifies an upper limit on the amount of data to ingest daily. |
23 | | add_tags | Adds a set of tags to the resource |
24 | | add_tag | Adds a tag to the resource |
25 |
26 | #### Configuration Members
27 |
28 | | Member | Purpose |
29 | |-|-|
30 | | CustomerID | Gets the ARM expression path to the customer ID of this LogAnalytics instance. |
31 | | CustomerID | Gets the ARM expression path to the primary shared key of this LogAnalytics instance. |
32 |
33 | #### Example
34 |
35 | ```fsharp
36 | open Farmer
37 | open Farmer.Builders
38 |
39 | let myAnalytics = logAnalytics {
40 | name "myloganalytics"
41 | retention_period 30
42 | enable_ingestion
43 | enable_query
44 | daily_cap 5
45 | add_tag "tag1" "myTestResourceFarmer"
46 | }
47 |
48 | let deployment = arm {
49 | location Location.WestEurope
50 | add_resource myAnalytics
51 | }
52 | ```
53 |
--------------------------------------------------------------------------------
/samples/scripts/keyvault.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.KeyVault
6 | open System
7 |
8 | let policy = accessPolicy {
9 | object_id Guid.Empty
10 | certificate_permissions [ Certificate.List ]
11 | secret_permissions Secret.All
12 | key_permissions [ Key.List ]
13 | }
14 |
15 | let complexSecret = secret {
16 | name "myComplexSecret"
17 | content_type "application/text"
18 | enable_secret
19 | activation_date (DateTime.Today.AddDays -1.)
20 | expiration_date (DateTime.Today.AddDays 1.)
21 | }
22 |
23 | let store = storageAccount { name "foo" }
24 | let principal = PrincipalId(ArmExpression.create "GETS BACK OBJECT ID OF ACCOUNT")
25 |
26 | let vault = keyVault {
27 | name "MyVault"
28 | sku Sku.Standard
29 | tenant_id Subscription.TenantId
30 |
31 | enable_disk_encryption_access
32 | enable_resource_manager_access
33 | enable_soft_delete_with_purge_protection
34 |
35 | add_access_policy policy
36 |
37 | add_access_policies [
38 | AccessPolicy.create principal
39 | AccessPolicy.create (principal, Secret.All)
40 | accessPolicy {
41 | object_id principal
42 | secret_permissions [ Secret.Get; Secret.Delete ]
43 | }
44 | ]
45 |
46 | disable_vm_access
47 | enable_recovery_mode
48 | enable_azure_services_bypass
49 |
50 | allow_default_traffic
51 |
52 | add_secret complexSecret
53 | add_secret "simpleSecret"
54 | add_secrets [ "firstSecret"; "secondSecret" ]
55 | add_secret ("thirdSecret", store.Key)
56 | add_tag "test" "test"
57 | }
58 |
59 | vault.Policies
60 |
61 | let deployment = arm {
62 | location Location.NorthEurope
63 | add_resource vault
64 | output "vault_uri" vault.VaultUri
65 | }
66 |
67 | deployment
68 | |> Writer.quickWrite (System.IO.Path.GetFileNameWithoutExtension __SOURCE_FILE__)
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/iot-hub.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "IOT Hub"
3 | date: 2020-05-19T23:14:14+02:00
4 | chapter: false
5 | weight: 9
6 | ---
7 |
8 | #### Overview
9 | The IOT Hub builder creates IOT Hub and linked Provision Services.
10 |
11 | * IOT Hubs (`Microsoft.Devices/IotHubs`)
12 | * Provisioning Services (`Microsoft.Devices/provisioningServices`)
13 |
14 | #### Builder Keywords
15 |
16 | | Keyword | Purpose |
17 | |-|-|
18 | | name | Specifies the name of the IOT Hub |
19 | | sku | Sets the SKU of the IOT Hub |
20 | | capacity | Sets the name of the capacity for the IOT Hub instance |
21 | | partition_count | Sets the name of the SKU/Tier for the IOT Hub instance |
22 | | retention_days | Sets the name of the SKU/Tier for the IOT Hub instance |
23 | | enable_device_provisioning | Sets the name of the SKU/Tier for the IOT Hub instance |
24 |
25 | #### Configuration Members
26 |
27 | | Member | Purpose |
28 | |-|-|
29 | | GetKey | Returns an ARM expression to retrieve the IOT Hub key for a specific policy e.g IotHubOwner or RegistryReadWrite. Useful for e.g. supplying the key to another resource e.g. KeyVault or an app setting in the App Service. |
30 | | GetConnectionString | Returns an ARM expression to generate an IOT Hub connection string for a specific policy e.g IotHubOwner or RegistryReadWrite. Useful for e.g. supplying the key to another resource e.g. KeyVault or an app setting in the App Service. |
31 |
32 | #### Example
33 |
34 | ```fsharp
35 | open Farmer
36 | open Farmer.Builders
37 |
38 | let hub = iotHub {
39 | name "yourhubname"
40 | sku IotHub.B1
41 | capacity 2
42 | partition_count 2
43 | retention_days 3
44 | enable_device_provisioning
45 | }
46 |
47 | let deployment = arm {
48 | location Location.NorthEurope
49 | add_resource hub
50 | output "iot_key" (hub.GetKey IotHub.IotHubOwner)
51 | output "iot_connection" (hub.GetConnectionString IotHub.RegistryReadWrite)
52 | }
53 | ```
--------------------------------------------------------------------------------
/samples/scripts/container-instance.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let containerGroupUser = userAssignedIdentity { name "aciUser" }
7 |
8 | let template = arm {
9 | location Location.WestEurope
10 |
11 | add_resources [
12 | containerGroupUser
13 | containerGroup {
14 | name "container-group-with-init"
15 | operating_system Linux
16 | restart_policy ContainerGroup.AlwaysRestart
17 | add_volumes [ volume_mount.empty_dir "html" ]
18 |
19 | add_init_containers [
20 | initContainer {
21 | name "init-stuff"
22 | image "debian"
23 | add_volume_mount "html" "/usr/share/nginx/html"
24 |
25 | command_line [
26 | "/bin/sh"
27 | "-c"
28 | "mkdir -p /usr/share/nginx/html && echo 'hello there' >> /usr/share/nginx/html/index.html"
29 | ]
30 | }
31 | ]
32 |
33 | add_identity containerGroupUser
34 |
35 | add_instances [
36 | containerInstance {
37 | name "nginx"
38 | image "nginx:alpine"
39 | add_volume_mount "html" "/usr/share/nginx/html"
40 |
41 | add_public_ports [ 80us; 443us ]
42 | add_internal_ports [ 123us ]
43 |
44 | memory 0.5
45 | cpu_cores 0.2
46 |
47 | probes [
48 | liveness {
49 | http "http://localhost:80/index.html"
50 | initial_delay_seconds 15
51 | }
52 | ]
53 | }
54 | ]
55 | }
56 | ]
57 | }
58 |
59 | Writer.quickWrite "container-instance" template
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/nat-gateway.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "NAT Gateway"
3 | date: 2022-08-02T22:26:00-04:00
4 | chapter: false
5 | weight: 5
6 | ---
7 |
8 | #### Overview
9 | The `natGateway` builder creates a NAT Gateway to efficiently manage the SNAT traffic used by resources
10 | in a virtual network. By default, it creates a single static public IP for the NAT Gateway, but more IP
11 | addresses or prefixes of groups of addresses can be specified.
12 |
13 | * NatGateway (`Microsoft.Network/natGateways`)
14 |
15 | #### Builder Keywords
16 |
17 | | Applies To | Keyword | Purpose |
18 | |-|--------------|----------------------------------------------------------------------------------------------------|
19 | | natGateway | name | Name of the NAT Gateway resource |
20 | | natGateway | idle_timeout | Timeout after which connections that have seen no traffic will be disconnected to free SNAT ports. |
21 | | natGateway | sku | Set the SKU of NAT Gateway to deploy |
22 |
23 | #### Example
24 |
25 | ```fsharp
26 | #r "nuget:Farmer"
27 |
28 | open Farmer
29 | open Farmer.Builders
30 |
31 | arm {
32 | location Location.EastUS
33 | add_resources [
34 | natGateway {
35 | name "my-nat-gateway"
36 | sku NatGateway.Sku.Standard
37 | }
38 | vnet {
39 | name "my-net"
40 | add_address_spaces [ "10.100.0.0/16" ]
41 | add_subnets [
42 | subnet {
43 | name "my-services"
44 | prefix "10.100.12.0/24"
45 | nat_gateway (Farmer.Arm.Network.natGateways.resourceId "my-nat-gateway")
46 | }
47 | ]
48 | }
49 | ]
50 | }
51 | ```
52 |
--------------------------------------------------------------------------------
/src/Farmer/Arm/Cache.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.Cache
3 |
4 | open Farmer
5 | open Farmer.Redis
6 |
7 | let redis = ResourceType("Microsoft.Cache/Redis", "2018-03-01")
8 |
9 | type Redis = {
10 | Name: ResourceName
11 | Location: Location
12 | Sku: {| Sku: Sku; Capacity: int |}
13 | RedisConfiguration: Map
14 | NonSslEnabled: bool option
15 | ShardCount: int option
16 | MinimumTlsVersion: TlsVersion option
17 | Tags: Map
18 | } with
19 |
20 | member this.Family =
21 | match this.Sku.Sku with
22 | | Basic
23 | | Standard -> 'C'
24 | | Premium -> 'P'
25 |
26 | interface IArmResource with
27 | member this.ResourceId = redis.resourceId this.Name
28 |
29 | member this.JsonModel = {|
30 | redis.Create(this.Name, this.Location, tags = this.Tags) with
31 | properties = {|
32 | sku = {|
33 | name = string this.Sku.Sku
34 | family = this.Family
35 | capacity = this.Sku.Capacity
36 | |}
37 | enableNonSslPort = this.NonSslEnabled |> Option.toNullable
38 | shardCount = this.ShardCount |> Option.toNullable
39 | minimumTlsVersion =
40 | // TLS 1.3 is supported, but it can only currently enforce TLS 1.2 as the minimum version.
41 | // Reference: https://learn.microsoft.com/azure/redis/tls-configuration#tls-13-support
42 | this.MinimumTlsVersion
43 | |> Option.map (function
44 | | Tls12 -> "1.2"
45 | | Tls13 -> "1.2") // TLS 1.3 enforcement not yet supported
46 | |> Option.toObj
47 | redisConfiguration = this.RedisConfiguration
48 | |}
49 | |}
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/availability-tests.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "App Insights - Availability Tests"
3 | date: 2021-08-06T07:00:00+01:00
4 | weight: 1
5 | chapter: false
6 | ---
7 |
8 | #### Overview
9 | The App Insights - Availability Tests builder is used to create Application Insights Availability Tests. You will need an Application Insights instance to run the tests.
10 | The tests can be just pinging the website and expecting a response code of 200, or they can be recorded Visual Studio WebTests as custom XML strings.
11 |
12 | * Application Insights (`Microsoft.Insights/webtests`)
13 |
14 | #### Builder Keywords
15 |
16 | | Keyword | Purpose |
17 | |-|-|
18 | | name | Sets the name of this Webtest instance. |
19 | | link_to_app_insights | Name or resource of the App Insight instance. |
20 | | web_test | AvailabilityTest.WebsiteUrl Uri to website, or AvailabilityTest.CustomWebtestXml string |
21 | | locations | List of locations where the site is pinged. These are not format of Farmer.Location but AvailabilityTest.TestSiteLocation. |
22 | | timeout | Timeout if the test is not responding. Default: 120 seconds. |
23 | | frequency | Frequency how often the test is run. Default: 900 seconds. |
24 |
25 | #### Example
26 |
27 | ```fsharp
28 | open Farmer
29 | open Farmer.Builders
30 |
31 | let ai = appInsights { name "ai" }
32 | let myAvailabilityTest =
33 | availabilityTest {
34 | name "avTest"
35 | link_to_app_insights ai
36 | timeout 60
37 | frequency 800
38 | locations [
39 | AvailabilityTest.TestSiteLocation.NorthEurope
40 | AvailabilityTest.TestSiteLocation.WestEurope
41 | AvailabilityTest.TestSiteLocation.CentralUS
42 | AvailabilityTest.TestSiteLocation.UKSouth
43 | ]
44 | web_test (
45 | "https://mywebsite.com"
46 | |> System.Uri
47 | |> AvailabilityTest.WebsiteUrl)
48 | }
49 | ```
50 |
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/private-endpoint.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Private Endpoint"
3 | date: 2022-08-05T16:13:00-04:00
4 | chapter: false
5 | weight: 12
6 | ---
7 |
8 | #### Overview
9 | The Private Endpoint builder (`privateEndpoint`) creates a private endpoint for accessing Azure resources or a private link service without traversing the Internet.
10 |
11 | * Private Endpoint (`Microsoft.Network/privateEndpoints`)
12 |
13 | #### Builder Keywords
14 |
15 | | Applies To | Keyword | Purpose |
16 | |-|-|-|
17 | | privateEndpoint | name | Specifies the name of the private endpoint. |
18 | | privateEndpoint | subnet_reference | Attaches the private endpoint to a referenced subnet. |
19 | | privateEndpoint | link_to_subnet | Attaches the private endpoint to a subnet deployed in the same deployment. |
20 | | privateEndpoint | link_to_unmanaged_subnet | Attaches the private endpoint to an existing subnet. |
21 | | privateEndpoint | resource | Specifies the ARM resource ID of the service it is connecting to. |
22 | | privateEndpoint | custom_nic_name | Optionally specify the name for the NIC generated for the private endpoint. |
23 | | privateEndpoint | add_group_ids | Specify one or more group IDs the private link service provides. |
24 |
25 | #### Configuration Members
26 |
27 | | Member | Purpose |
28 | |-|-|
29 | | CustomNicEndpointIP | If the `custom_nic_name` is set, this gets an ARM Expression to get the private endpoint IP address by 0-based index. |
30 | | CustomNicFirstEndpointIP | If the `custom_nic_name` is set, this gets an ARM Expression to get the first private endpoint IP address. |
31 |
32 | #### Example
33 |
34 | ```fsharp
35 | open Farmer
36 | open Farmer.Builders
37 |
38 | let myPrivateEndpoint = privateEndpoint {
39 | name "private-endpoint"
40 | custom_nic_name "private-endpoint-nic"
41 | link_to_subnet (subnets.resourceId (ResourceName "my-net", ResourceName "priv-endpoints" ))
42 | resource (Unmanaged existingPrivateLinkId)
43 | }
44 | ```
45 |
--------------------------------------------------------------------------------
/samples/scripts/aks.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open System
4 | open System.IO
5 | open Farmer
6 | open Farmer.Builders
7 | open Farmer.ContainerService
8 | open Farmer.Vm
9 |
10 | let homeDir = Environment.GetFolderPath Environment.SpecialFolder.UserProfile
11 |
12 | let pubKey =
13 | [ homeDir; ".ssh"; "id_rsa.pub" ]
14 | |> String.concat (string Path.DirectorySeparatorChar)
15 | |> File.ReadAllText
16 |
17 | let aksSubnet = "containernet"
18 |
19 | let vnetName = sprintf "env%0i-vnet"
20 | let aksName = sprintf "env%0i-aks"
21 | let aksDns = aksName
22 |
23 | let makeVnet (n: int) =
24 | vnet {
25 | name (vnetName n)
26 | add_address_spaces [ "10.1.0.0/16" ]
27 |
28 | add_subnets [
29 | subnet {
30 | name "default"
31 | prefix "10.1.0.0/24"
32 | }
33 | subnet {
34 | name aksSubnet
35 | prefix "10.1.30.0/25"
36 | }
37 | ]
38 | }
39 | :> IBuilder
40 |
41 | let msi = userAssignedIdentity { name "aks-user" }
42 |
43 | let makeAks (n: int) =
44 | aks {
45 | name (aksName n)
46 | tier Tier.Standard
47 | dns_prefix (aksDns n)
48 | enable_rbac
49 | add_identity msi
50 |
51 | add_agent_pools [
52 | agentPool {
53 | name "linuxPool"
54 | vm_size VMSize.Standard_D2_v5
55 | count 3
56 | vnet (vnetName n)
57 | subnet aksSubnet
58 | }
59 | ]
60 |
61 | network_profile (azureCniNetworkProfile { service_cidr "10.250.0.0/16" })
62 | linux_profile "aksuser" pubKey
63 | service_principal_use_msi
64 | }
65 | :> IBuilder
66 |
67 | let vnets = [ 1..4 ] |> Seq.map makeVnet |> List.ofSeq
68 | let akses = [ 1..4 ] |> Seq.map makeAks |> List.ofSeq
69 |
70 | arm {
71 | add_resource msi
72 | add_resources akses
73 | add_resources vnets
74 | }
75 | |> Writer.quickWrite "aks-on-vnet"
76 |
--------------------------------------------------------------------------------
/src/Farmer/Builders/Builders.LogicApps.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Builders.LogicApps
3 |
4 | open Farmer
5 | open Farmer.Arm.LogicApps
6 | open System.IO
7 | open System.Text.Json
8 |
9 | type Definition =
10 | | FileDefinition of path: string
11 | | ValueDefinition of definition: string
12 |
13 | type LogicAppConfig = {
14 | WorkflowName: ResourceName
15 | Definition: Definition
16 | Tags: Map
17 | } with
18 |
19 | member this.LogicAppWorkflowName = workflows.resourceId(this.WorkflowName).Name
20 |
21 | interface IBuilder with
22 | member this.ResourceId = workflows.resourceId this.WorkflowName
23 |
24 | member this.BuildResources location = [
25 | {
26 | Name = this.LogicAppWorkflowName
27 | Location = location
28 | Definition =
29 | match this.Definition with
30 | | FileDefinition path ->
31 | let fileContent = File.ReadAllText(path)
32 | JsonDocument.Parse(fileContent)
33 | | ValueDefinition value -> JsonDocument.Parse(value)
34 | Tags = this.Tags
35 | }
36 | ]
37 |
38 | type LogicAppBuilder() =
39 | member _.Yield _ = {
40 | WorkflowName = ResourceName "logic-app-workflow"
41 | Definition = ValueDefinition """{"name":"logic-app-workflow"}"""
42 | Tags = Map.empty
43 | }
44 |
45 | []
46 | member _.Name(state: LogicAppConfig, name) = {
47 | state with
48 | WorkflowName = ResourceName name
49 | }
50 |
51 | []
52 | member _.Definition(state: LogicAppConfig, definition: Definition) = { state with Definition = definition }
53 |
54 | interface ITaggable with
55 | member _.Add state tags = {
56 | state with
57 | Tags = state.Tags |> Map.merge tags
58 | }
59 |
60 | let logicApp = LogicAppBuilder()
--------------------------------------------------------------------------------
/src/Tests/test-data/azure-firewall.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "outputs": {},
5 | "parameters": {},
6 | "resources": [
7 | {
8 | "apiVersion": "2020-07-01",
9 | "dependsOn": [
10 | "[resourceId('Microsoft.Network/virtualHubs', 'farmer_vhub')]"
11 | ],
12 | "location": "northeurope",
13 | "name": "farmer_firewall",
14 | "properties": {
15 | "hubIPAddresses": {
16 | "publicIPs": {
17 | "addresses": [],
18 | "count": 2
19 | }
20 | },
21 | "sku": {
22 | "name": "AZFW_Hub",
23 | "tier": "Standard"
24 | },
25 | "virtualHub": {
26 | "id": "[resourceId('Microsoft.Network/virtualHubs', 'farmer_vhub')]"
27 | }
28 | },
29 | "type": "Microsoft.Network/azureFirewalls",
30 | "zones": [
31 | "1",
32 | "2"
33 | ]
34 | },
35 | {
36 | "apiVersion": "2020-07-01",
37 | "dependsOn": [
38 | "[resourceId('Microsoft.Network/virtualWans', 'farmer-vwan')]"
39 | ],
40 | "location": "northeurope",
41 | "name": "farmer_vhub",
42 | "properties": {
43 | "addressPrefix": "100.73.255.0/24",
44 | "routeTable": {
45 | "routes": []
46 | },
47 | "sku": "Standard",
48 | "virtualWan": {
49 | "id": "[resourceId('Microsoft.Network/virtualWans', 'farmer-vwan')]"
50 | }
51 | },
52 | "type": "Microsoft.Network/virtualHubs"
53 | },
54 | {
55 | "apiVersion": "2020-07-01",
56 | "location": "northeurope",
57 | "name": "farmer-vwan",
58 | "properties": {
59 | "allowBranchToBranchTraffic": true,
60 | "disableVpnEncryption": true,
61 | "office365LocalBreakoutCategory": "None",
62 | "type": "Standard"
63 | },
64 | "type": "Microsoft.Network/virtualWans"
65 | }
66 | ]
67 | }
--------------------------------------------------------------------------------
/docs/content/contributing/create-pull-requests.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Creating Pull Requests"
3 | draft: false
4 | weight: 5
5 | ---
6 |
7 | > This article is not a detailed guide on how to create a pull request (PR). See [here](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) to learn more about how to work with pull requests on GitHub.
8 |
9 | The purpose of this article is to illustrate the main checklists you must go through before a PR will be considered for inclusion in Farmer. If you are new to Farmer, F# or GitHub - **don't worry**. The team will be happy to support you in getting your feature over the line.
10 |
11 | These are the following checks we'll normally put in place:
12 |
13 | #### 1. Create an issue first!
14 | Except for small pull requests, create an issue to discuss the feature. The last thing we want is for someone to spend hours of their time on a feature only for someone else to have started work on something similar, or for the admins of the project to reject it for whatever reason e.g. does not fit with the project etc. Creating an issue does not take long and will help save time for everyone.
15 | #### 2. Create Documentation
16 | Every PR to Farmer **must** have some documentation with it. If you modify a resource and add a new keyword, it **must** be added to the appropriate docs page.
17 | #### 3. Write Unit Tests
18 | Every PR to Farmer **should** have at least one test associated with it. If no tests are added, you can expect at least a request for one or an explanation as to why one is not necessary.
19 | #### 4. Write Release Notes
20 | Every PR to Farmer **must** include an entry to the `RELEASE_NOTES.md` file under the next release. Briefly explain the feature and ideally link to the PR number e.g.
21 | #### 5. Adhere to Coding Standards
22 | Here are some (very basic!) standards for the project:
23 |
24 | 1. Follow the coding style of the existing source.
25 | 2. Use 4 spaces for indentation.
26 | 3. As a last resort, adhere to [official](https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/) style guide as a basis.
27 |
--------------------------------------------------------------------------------
/src/Farmer/Arm/LogAnalytics.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.LogAnalytics
3 |
4 | open Farmer
5 |
6 | let workspaces =
7 | ResourceType("Microsoft.OperationalInsights/workspaces", "2020-03-01-preview")
8 |
9 | type Workspace = {
10 | Name: ResourceName
11 | Location: Location
12 | RetentionPeriod: int option
13 | IngestionSupport: FeatureFlag option
14 | QuerySupport: FeatureFlag option
15 | DailyCap: int option
16 | Tags: Map
17 | } with
18 |
19 | interface IArmResource with
20 | member this.ResourceId = workspaces.resourceId this.Name
21 |
22 | member this.JsonModel = {|
23 | workspaces.Create(this.Name, this.Location, tags = this.Tags) with
24 | properties = {|
25 | sku = {| name = "PerGB2018" |}
26 | retentionInDays = this.RetentionPeriod |> Option.toNullable
27 | workspaceCapping =
28 | match this.DailyCap with
29 | | None -> null
30 | | Some cap -> {| dailyQuotaGb = cap |} |> box
31 | publicNetworkAccessForIngestion = this.IngestionSupport |> Option.map _.ArmValue |> Option.toObj
32 | publicNetworkAccessForQuery = this.QuerySupport |> Option.map _.ArmValue |> Option.toObj
33 | |}
34 | |}
35 |
36 | type LogAnalytics =
37 | static member getCustomerId resourceId =
38 | ArmExpression.reference(workspaces, resourceId).Map(fun r -> r + ".customerId").WithOwner(resourceId)
39 |
40 | static member getCustomerId(name, ?resourceGroup) =
41 | LogAnalytics.getCustomerId (ResourceId.create (workspaces, name, ?group = resourceGroup))
42 |
43 | static member getPrimarySharedKey resourceId =
44 | ArmExpression.listKeys(workspaces, resourceId).Map(fun r -> r + ".primarySharedKey").WithOwner(resourceId)
45 |
46 | static member getPrimarySharedKey(name, ?resourceGroup) =
47 | LogAnalytics.getPrimarySharedKey (ResourceId.create (workspaces, name, ?group = resourceGroup))
--------------------------------------------------------------------------------
/samples/scripts/container-app.fsx:
--------------------------------------------------------------------------------
1 | #r @"../../src/Farmer/bin/Debug/netstandard2.0/Farmer.dll"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 | open Farmer.ContainerApp
6 | open System
7 |
8 | let queueName = "myqueue"
9 | let storageName = $"{Guid.NewGuid().ToString().[0..5]}containerqueue"
10 |
11 | let myStorageAccount = storageAccount {
12 | name storageName
13 | add_queue queueName
14 | add_file_share "certs"
15 | }
16 |
17 | let env = containerEnvironment {
18 | name $"containerenv{Guid.NewGuid().ToString().[0..5]}"
19 |
20 | add_containers [
21 | containerApp {
22 | name "aspnetsample"
23 | add_simple_container "mcr.microsoft.com/dotnet/samples" "aspnetapp"
24 | ingress_target_port 80us
25 | ingress_transport Auto
26 | add_http_scale_rule "http-scaler" { ConcurrentRequests = 10 }
27 | add_cpu_scale_rule "cpu-scaler" { Utilization = 50 }
28 | }
29 | containerApp {
30 | name "queuereaderapp"
31 |
32 | add_volumes [
33 | Volume.emptyDir "empty-v"
34 | Volume.azureFile "certs-v" (ResourceName "certs") myStorageAccount.Name StorageAccessMode.ReadOnly
35 | ]
36 |
37 | add_containers [
38 | container {
39 | name "queuereaderapp"
40 | public_docker_image "mcr.microsoft.com/azuredocs/containerapps-queuereader" "latest"
41 | cpu_cores 0.25
42 | memory 0.5
43 | ephemeral_storage 1.
44 | add_volume_mounts [ "empty-v", "/tmp"; "certs-v", "/certs" ]
45 | }
46 | ]
47 |
48 | replicas 1 10
49 | add_env_variable "QueueName" queueName
50 | add_secret_expression "queueconnectionstring" myStorageAccount.Key
51 | }
52 | ]
53 | }
54 |
55 | let template = arm {
56 | location Location.NorthEurope
57 | add_resources [ env; myStorageAccount ]
58 | }
59 |
60 | template |> Deploy.execute "containerappsdemo" []
--------------------------------------------------------------------------------
/src/Farmer/Builders/Builders.BingSearch.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Builders.BingSearch
3 |
4 | open Farmer
5 | open Farmer.Arm.BingSearch
6 | open Farmer.BingSearch
7 |
8 | type BingSearch =
9 | /// Gets an ARM Expression key for any Bing Search instance.
10 | static member getKey(resourceId: ResourceId) =
11 | ArmExpression.create ($"listKeys({resourceId.ArmExpression.Value}, '{accounts.ApiVersion}').key1", resourceId)
12 |
13 | static member getKey(name: ResourceName) =
14 | BingSearch.getKey (accounts.resourceId name)
15 |
16 | type BingSearchConfig = {
17 | Name: ResourceName
18 | Sku: Sku
19 | Tags: Map
20 | Statistics: FeatureFlag
21 | } with
22 |
23 | /// Gets an ARM expression to the key of this Bing Search instance.
24 | member this.Key = BingSearch.getKey (accounts.resourceId this.Name)
25 |
26 | interface IBuilder with
27 | member this.ResourceId = accounts.resourceId this.Name
28 |
29 | member this.BuildResources location = [
30 | {
31 | Name = this.Name
32 | Location = location
33 | Sku = this.Sku
34 | Tags = this.Tags
35 | Statistics = this.Statistics
36 | }
37 | ]
38 |
39 | type BingSearchBuilder() =
40 | member _.Yield _ = {
41 | Name = ResourceName.Empty
42 | Sku = F1
43 | Tags = Map.empty
44 | Statistics = FeatureFlag.Disabled
45 | }
46 |
47 | []
48 | member _.Name(state: BingSearchConfig, name) = { state with Name = ResourceName name }
49 |
50 | []
51 | member _.Sku(state: BingSearchConfig, sku) = { state with Sku = sku }
52 |
53 | []
54 | member _.EnableStatistics(state: BingSearchConfig, value) = { state with Statistics = value }
55 |
56 | interface ITaggable with
57 | member _.Add state tags = {
58 | state with
59 | Tags = state.Tags |> Map.merge tags
60 | }
61 |
62 | let bingSearch = BingSearchBuilder()
--------------------------------------------------------------------------------
/samples/scripts/dns.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget:Farmer"
2 |
3 | open Farmer
4 | open Farmer.Builders
5 |
6 | let dns = dnsZone {
7 | name "farmer.com"
8 | zone_type Dns.Public
9 |
10 | add_records [
11 | cnameRecord {
12 | name "www2"
13 | ttl 3600
14 | cname "farmer.github.com"
15 | }
16 | aRecord {
17 | name "aName"
18 | ttl 7200
19 | add_ipv4_addresses [ "192.168.0.1"; "192.168.0.2" ]
20 | }
21 | aaaaRecord {
22 | name "aaaaName"
23 | ttl 7200
24 | add_ipv6_addresses [ "2001:0db8:85a3:0000:0000:8a2e:0370:7334" ]
25 | }
26 | txtRecord {
27 | name "txtName"
28 | ttl 3600
29 | add_values [ "v=spf1 include:spf.protection.outlook.com -all" ]
30 | }
31 | mxRecord {
32 | name "mxName"
33 | ttl 7200
34 |
35 | add_values [
36 | 0, "farmer-com.mail.protection.outlook.com"
37 | 1, "farmer2-com.mail.protection.outlook.com"
38 | ]
39 | }
40 | nsRecord {
41 | name "nsRecord"
42 | ttl 172800
43 | add_nsd_names [ "my.other.dns.com." ]
44 | }
45 | soaRecord {
46 | host "ns1-09.azure-dns.com."
47 | ttl 3600
48 | email "test.microsoft.com"
49 | serial_number 1L
50 | minimum_ttl 2L
51 | refresh_time 3L
52 | retry_time 4L
53 | expire_time 5L
54 | }
55 | srvRecord {
56 | name "_sip._tcp.name"
57 | ttl 3600
58 |
59 | add_values [
60 | {
61 | Priority = Some 100
62 | Weight = Some 1
63 | Port = Some 5061
64 | Target = Some "farmer.online.com."
65 | }
66 | ]
67 | }
68 | ]
69 | }
70 |
71 | let deployment = arm {
72 | location Location.NorthEurope
73 | add_resource dns
74 | }
75 |
76 | deployment |> Writer.quickWrite "dns-example"
--------------------------------------------------------------------------------
/src/Tests/StaticWebApp.fs:
--------------------------------------------------------------------------------
1 | module StaticWebApp
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.Builders
6 | open Farmer.WebApp
7 | open Farmer.Arm
8 | open System
9 |
10 | let tests =
11 | testList "Static Web App Tests" [
12 | test "Creates a basic static web app" {
13 | let swa = staticWebApp {
14 | name "foo"
15 | api_location "api"
16 | app_location "app"
17 | artifact_location "artifact"
18 | branch "feature"
19 | repository "https://compositional-it.com"
20 | }
21 |
22 | let swaArm =
23 | (swa :> IBuilder).BuildResources(Location.WestEurope).[0] :?> StaticSite
24 |
25 | Expect.equal swaArm.ApiLocation (Some "api") "Api"
26 | Expect.equal swaArm.Name (ResourceName "foo") "Name"
27 | Expect.equal swaArm.AppLocation "app" "AppLocation"
28 | Expect.equal swaArm.AppArtifactLocation (Some "artifact") "ArtifactLocation"
29 | Expect.equal swaArm.Branch "feature" "Branch"
30 | Expect.equal swaArm.Repository (Uri "https://compositional-it.com") "Repository"
31 | }
32 | test "Defaults to master branch" {
33 | let swa = staticWebApp {
34 | name "foo"
35 | repository "https://compositional-it.com"
36 | }
37 |
38 | let swaArm =
39 | (swa :> IBuilder).BuildResources(Location.WestEurope).[0] :?> StaticSite
40 |
41 | Expect.equal swaArm.Branch "master" "Branch"
42 | }
43 | test "Supports app settings" {
44 | let swa = staticWebApp {
45 | name "foo"
46 | repository "https://compositional-it.com"
47 | app_settings [ "foo", "bar"; "blip", "blop" ]
48 |
49 | }
50 |
51 | let swaArm =
52 | (swa :> IBuilder).BuildResources(Location.WestEurope).[1] :?> StaticSites.Config
53 |
54 | Expect.equal swaArm.Properties (Map [ "foo", "bar"; "blip", "blop" ]) "App Settings not set"
55 | }
56 | ]
--------------------------------------------------------------------------------
/src/Tests/Helpers.fs:
--------------------------------------------------------------------------------
1 | []
2 | module TestHelpers
3 |
4 | open Farmer
5 | open Microsoft.Rest.Serialization
6 |
7 | let createSimpleDeployment parameters = {
8 | Location = Location.NorthEurope
9 | PostDeployTasks = []
10 | Template = {
11 | Outputs = []
12 | Parameters = parameters |> List.map SecureParameter
13 | Resources = []
14 | }
15 | RequiredResourceGroups = []
16 | Tags = Map.empty
17 | }
18 |
19 | let convertTo<'T> = Serialization.toJson >> Serialization.ofJson<'T>
20 |
21 | let farmerToMs<'T when 'T: null> (serializationSettings: Newtonsoft.Json.JsonSerializerSettings) data =
22 | data
23 | |> Serialization.toJson
24 | |> fun json -> SafeJsonConvert.DeserializeObject<'T>(json, serializationSettings)
25 |
26 | let getResourceAtIndex serializationSettings index (builder: #IBuilder) =
27 | builder.BuildResources Location.WestEurope
28 | |> fun r -> r.[index].JsonModel |> farmerToMs serializationSettings
29 |
30 | let findAzureResources<'T when 'T: null>
31 | (serializationSettings: Newtonsoft.Json.JsonSerializerSettings)
32 | (deployment: IDeploymentSource)
33 | =
34 | let template =
35 | deployment.Deployment.Template |> Writer.TemplateGeneration.processTemplate
36 |
37 | template.resources
38 | |> Seq.map Serialization.toJson
39 | |> Seq.choose (fun json ->
40 | SafeJsonConvert.DeserializeObject<'T>(json, serializationSettings)
41 | |> Option.ofObj)
42 | |> Seq.toList
43 |
44 | type TypedArmTemplate<'ResT> = { Resources: 'ResT array }
45 |
46 | let getFirstResourceOrFail (template: TypedArmTemplate<'ResourceType>) =
47 | if Array.length template.Resources < 1 then
48 | raiseFarmer "Template had no resources"
49 |
50 | template.Resources.[0]
51 |
52 | let toTemplate loc (d: IBuilder) =
53 | let a = arm {
54 | location loc
55 | add_resource d
56 | }
57 |
58 | a.Template
59 |
60 | let toTypedTemplate<'ResourceType> loc =
61 | toTemplate loc
62 | >> Writer.toJson
63 | >> Serialization.ofJson>
64 | >> getFirstResourceOrFail
--------------------------------------------------------------------------------
/src/Farmer/Arm/Disk.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.Disk
3 |
4 | open System
5 | open Farmer
6 |
7 | let disks = ResourceType("Microsoft.Compute/disks", "2022-07-02")
8 |
9 | type DiskCreation =
10 | | Import of SourceUri: Uri * StorageAccountId: ResourceId
11 | | Empty of Size: int
12 |
13 | type Disk = {
14 | Name: ResourceName
15 | Location: Location
16 | Sku: Vm.DiskType option
17 | Zones: string list
18 | OsType: OS
19 | CreationData: DiskCreation
20 | Tags: Map
21 | Dependencies: ResourceId Set
22 | } with
23 |
24 | interface IArmResource with
25 | member this.ResourceId = disks.resourceId this.Name
26 |
27 | member this.JsonModel = {|
28 | disks.Create(this.Name, this.Location, dependsOn = this.Dependencies, tags = this.Tags) with
29 | sku =
30 | this.Sku
31 | |> Option.map (fun sku -> {| name = sku.ArmValue |} :> obj)
32 | |> Option.toObj
33 | zones = if this.Zones.IsEmpty then null else ResizeArray(this.Zones)
34 | properties = {|
35 | creationData =
36 | match this.CreationData with
37 | | Empty _ -> {| createOption = "Empty" |} :> obj
38 | | Import(sourceUri, storageAccountId) ->
39 | {|
40 | createOption = "Import"
41 | sourceUri = sourceUri.AbsoluteUri
42 | storageAccountId = storageAccountId.Eval()
43 | |}
44 | :> obj
45 | diskSizeGB =
46 | match this.CreationData with
47 | | Empty size -> size / 1 :> obj
48 | | _ -> null
49 | osType =
50 | this.OsType
51 | |> function
52 | | Linux -> "Linux"
53 | | Windows -> "Windows"
54 | |}
55 | |}
--------------------------------------------------------------------------------
/src/Farmer/Builders/Builders.CognitiveServices.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Builders.CognitiveServices
3 |
4 | open Farmer
5 | open Farmer.Arm.CognitiveServices
6 | open Farmer.CognitiveServices
7 |
8 | type CognitiveServices =
9 | /// Gets an ARM Expression key for any Cognitives Services instance.
10 | static member getKey(resourceId: ResourceId) =
11 | ArmExpression.create ($"listKeys({resourceId.ArmExpression.Value}, '{accounts.ApiVersion}').key1", resourceId)
12 |
13 | static member getKey(name: ResourceName) =
14 | CognitiveServices.getKey (accounts.resourceId name)
15 |
16 | type CognitiveServicesConfig = {
17 | Name: ResourceName
18 | Sku: Sku
19 | Api: Kind
20 | Tags: Map
21 | } with
22 |
23 | /// Gets an ARM expression to the key of this Cognitive Services instance.
24 | member this.Key = CognitiveServices.getKey (accounts.resourceId this.Name)
25 |
26 | interface IBuilder with
27 | member this.ResourceId = accounts.resourceId this.Name
28 |
29 | member this.BuildResources location = [
30 | {
31 | Name = this.Name
32 | Location = location
33 | Sku = this.Sku
34 | Kind = this.Api
35 | Tags = this.Tags
36 | }
37 | ]
38 |
39 | type CognitiveServicesBuilder() =
40 | member _.Yield _ = {
41 | Name = ResourceName.Empty
42 | Sku = F0
43 | Api = AllInOne
44 | Tags = Map.empty
45 | }
46 |
47 | []
48 | member _.Name(state: CognitiveServicesConfig, name) = { state with Name = ResourceName name }
49 |
50 | []
51 | member _.Sku(state: CognitiveServicesConfig, sku) = { state with Sku = sku }
52 |
53 | []
54 | member _.Api(state: CognitiveServicesConfig, api) = { state with Api = api }
55 |
56 | interface ITaggable with
57 | member _.Add state tags = {
58 | state with
59 | Tags = state.Tags |> Map.merge tags
60 | }
61 |
62 | let cognitiveServices = CognitiveServicesBuilder()
--------------------------------------------------------------------------------
/src/Tests/AzCli.fs:
--------------------------------------------------------------------------------
1 | module AzCli
2 |
3 | open Expecto
4 | open Farmer
5 | open System
6 | open TestHelpers
7 |
8 | let deployTo resourceGroupName parameters (deployment: IDeploymentSource) =
9 | printfn "Creating resource group %s..." resourceGroupName
10 |
11 | let deployResponse =
12 | deployment.Deployment |> Deploy.tryExecute resourceGroupName parameters
13 |
14 | let deleteResponse = Deploy.Az.delete resourceGroupName
15 |
16 | match deployResponse, deleteResponse with
17 | | Ok _, Ok _ -> ()
18 | | Error e, _ -> raiseFarmer $"Something went wrong during the deployment: {e}"
19 | | _, Error e -> raiseFarmer $"Something went wrong during the delete: {e}"
20 |
21 | let endToEndTests =
22 | testList "End to end tests" [
23 | test "Deploys and deletes a resource group" {
24 | let resourceGroupName = sprintf "farmer-integration-test-delete-%O" (Guid.NewGuid())
25 | arm { location Location.NorthEurope } |> deployTo resourceGroupName []
26 | }
27 |
28 | test "If parameters are missing, deployment is immediately rejected" {
29 | let deployment = createSimpleDeployment [ "p1" ]
30 | let result = deployment |> Deploy.tryExecute "sample-rg" []
31 | Expect.equal result (Error "The following parameters are missing: p1. Please add them.") ""
32 | }
33 | ]
34 |
35 | let tests =
36 | testList "Azure CLI" [
37 | test "Can connect to Az CLI" {
38 | match Deploy.Az.checkVersion Deploy.Az.MinimumVersion with
39 | | Ok _ -> ()
40 | | Error msg -> raiseFarmer $"Version check failed: {msg}"
41 | }
42 |
43 | test "Az output is always JSON" {
44 | // account list always defaults to table, regardless of defaults?
45 | Deploy.Az.az "account list --all"
46 | |> Result.map
47 | Serialization.ofJson<
48 | {|
49 | id: Guid
50 | tenantId: Guid
51 | isDefault: bool
52 | |} array
53 | >
54 | |> ignore
55 | }
56 | ]
--------------------------------------------------------------------------------
/src/Farmer/Writer.fs:
--------------------------------------------------------------------------------
1 | module Farmer.Writer
2 |
3 | open System.IO
4 | open System
5 | open System.Reflection
6 | open Farmer
7 |
8 | module TemplateGeneration =
9 | let processTemplate (template: ArmTemplate) = {|
10 | ``$schema`` = "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"
11 | contentVersion = "1.0.0.0"
12 | resources = template.Resources |> List.map _.JsonModel
13 | parameters =
14 | template.Parameters
15 | |> List.map (fun (SecureParameter p) -> p, {| ``type`` = "securestring" |})
16 | |> Map.ofList
17 | outputs =
18 | template.Outputs
19 | |> List.map (fun (k, v) -> k, {| ``type`` = "string"; value = v |})
20 | |> Map.ofList
21 | |}
22 |
23 | let branding () =
24 | let version =
25 | Assembly.GetExecutingAssembly().GetCustomAttribute().InformationalVersion
26 |
27 | printfn "=================================================="
28 | printfn "Farmer %s" version
29 | printfn "Repeatable deployments in Azure made easy!"
30 | printfn "=================================================="
31 |
32 | /// Returns a JSON string representing the supplied ARMTemplate.
33 | let toJson = TemplateGeneration.processTemplate >> Serialization.toJson
34 |
35 | /// Writes the provided JSON to a file based on the supplied template name. The postfix ".json" will automatically be added to the filename.
36 | let toFile folder templateName json =
37 | let filename =
38 | let filename = $"{templateName}.json"
39 | Path.Combine(folder, filename)
40 |
41 | let directory = Path.GetDirectoryName filename
42 |
43 | if not (Directory.Exists directory) then
44 | Directory.CreateDirectory directory |> ignore
45 |
46 | File.WriteAllText(filename, json)
47 | filename
48 |
49 | /// Converts the supplied ARMTemplate to JSON and then writes it out to the provided template name. The postfix ".json" will automatically be added to the filename.
50 | let quickWrite templateName (deployment: IDeploymentSource) =
51 | deployment.Deployment.Template |> toJson |> toFile "." templateName |> ignore
--------------------------------------------------------------------------------
/src/Tests/BingSearch.fs:
--------------------------------------------------------------------------------
1 | module BingSearch
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.Builders
6 | open Farmer.BingSearch
7 | open Farmer.Arm
8 | open System
9 | open TestHelpers
10 |
11 | let private asJson (arm: IArmResource) =
12 | arm.JsonModel
13 | |> convertTo<
14 | {|
15 | kind: string
16 | properties: {| statisticsEnabled: bool |}
17 | |}
18 | >
19 |
20 | let tests =
21 | testList "Bing Search" [
22 | test "Basic test" {
23 | let tags = [ "a", "1"; "b", "2" ]
24 |
25 | let swa = bingSearch {
26 | name "test"
27 | sku S0
28 | add_tags tags
29 | statistics Enabled
30 | }
31 |
32 | let baseArm = (swa :> IBuilder).BuildResources(Location.WestEurope).[0]
33 | let bsArm = baseArm :?> BingSearch.Accounts
34 | let jsonModel = asJson baseArm
35 | Expect.equal bsArm.Name (ResourceName "test") "Name"
36 | Expect.equal bsArm.Location Location.WestEurope "Location"
37 | Expect.isTrue jsonModel.properties.statisticsEnabled "Statistics enabled in json"
38 | Expect.equal bsArm.Statistics FeatureFlag.Enabled "Statistics enabled"
39 | Expect.equal bsArm.Sku S0 "Sku"
40 | Expect.equal jsonModel.kind "Bing.Search.v7" "kind"
41 | Expect.equal bsArm.Tags (Map tags) "Tags"
42 | }
43 |
44 | test "Default options test" {
45 | let swa = bingSearch { name "test" }
46 |
47 | let baseArm = (swa :> IBuilder).BuildResources(Location.WestEurope).[0]
48 | let bsArm = baseArm :?> BingSearch.Accounts
49 | let jsonModel = asJson baseArm
50 | Expect.equal bsArm.Name (ResourceName "test") "Name"
51 | Expect.equal bsArm.Location Location.WestEurope "Location"
52 | Expect.isFalse jsonModel.properties.statisticsEnabled "Statistics not enabled in json"
53 | Expect.equal bsArm.Statistics Disabled "Statistics not enabled"
54 | Expect.equal bsArm.Sku F1 "Sku"
55 | Expect.equal jsonModel.kind "Bing.Search.v7" "kind"
56 | Expect.isEmpty bsArm.Tags "Tags"
57 | }
58 | ]
--------------------------------------------------------------------------------
/docs/content/tutorials/webapp-deploy.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Deploy an ASP.NET app"
3 | date: 2020-10-24
4 | draft: false
5 | weight: 5
6 | ---
7 |
8 | #### Introduction
9 | This tutorial shows how to create the infrastructure required to host a ASP.NET web app, and how to automatically deploy that application with Farmer. We'll cover the following steps:
10 |
11 | 1. Creating and configuring a basic ASP.NET web application.
12 | 1. Creating a web app in Farmer.
13 | 1. Deploying the web app through Farmer.
14 |
15 | {{< figure src="../../images/tutorials/webapp.png" caption="[Full code available here](https://github.com/CompositionalIT/farmer/blob/master/samples/scripts/tutorials/webapp.fsx)">}}
16 |
17 | > Note: Your web application can be a C# web application - it does not need to be written in F#!
18 |
19 | #### Creating the ASP.NET web application
20 | Create a brand new ASP.NET web application:
21 |
22 | 1. Create a directory for your new application and enter it.
23 | 2. Using the dotnet SDK, create a new application: `dotnet new mvc`.
24 | 3. Notice that inside the project file (either `csproj` or `fsproj`), the Project SDK is already set to `Microsoft.NET.Sdk.Web`. This is more-or-less required for hosting in Azure.
25 | 4. Locally publish the application to a directory called `deploy`: `dotnet publish -c Release -o deploy`.
26 |
27 | > dotnet publish puts all built files and outputs into a single folder, and adds a `web.config` as required for e.g. Azure, as long as your Project SDK is set correctly.
28 |
29 | #### Create the Web App
30 | Create a new Farmer application which contains a web app.
31 |
32 | ```fsharp
33 | open Farmer
34 | open Farmer.Builders
35 |
36 | let webapplication = webApp {
37 | name ""
38 | }
39 | ```
40 |
41 | #### Configure Web App to deploy your ASP.NET application
42 | ```fsharp
43 | let webapplication = webApp {
44 | ...
45 | zip_deploy @""
46 | }
47 | ```
48 |
49 | #### That's it!
50 | Deploy the web app by adding it to an ARM builder and deploy it to a resource group of your choosing. During the deployment process, you will notice the following:
51 |
52 | `Running ZIP deploy for `
53 |
54 | > Farmer will automatically zip up the contents of the folder for you.
55 |
--------------------------------------------------------------------------------
/docs/content/testimonials/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Testimonials"
3 | date: 2020-06-27T17:57:05+02:00
4 | weight: 7
5 | ---
6 |
7 | #### Please submit a pull request [here](https://github.com/CompositionalIT/farmer/blob/master/docs/content/testimonials/_index.md) with details of your success stories of using Farmer!
8 |
9 | > "We've been using Farmer to help rapidly onboard our customers onto Azure with repeatable processes,
10 | > particularly with the SAFE Stack. It's helping our team adopt best practices without passing on
11 | > expense to our customers."
12 | >
13 | > **Isaac Abraham, Director, [Compositional IT](https://compositional-it.com)**
14 |
15 |
16 | > "Farmer quickly became an essential tool for Continuous Deployment at our F# projects.
17 | > Clean DSL, great documentation, growing support of various Azure services and PR-friendly
18 | > approach made Farmer to be one of the best open source projects in these days."
19 | >
20 | > **Roman Provazník, F# Lead Developer, [CN Group](https://cngroup.dk)**
21 |
22 | > "[Holy moly, this was a breeze!](https://twitter.com/Jan_de_V/status/1276250776042692627)! I'm SO going to use this more often, even if it's just to get a baseline for a customer. Saves tons of time!
23 | >
24 | > **[Jan De Vries](https://twitter.com/Jan_de_V), Microsoft MVP**
25 |
26 | > "[Hey @isaac_abraham, #Farmer is kind of awesome](https://twitter.com/janekf/status/1305518187115696129). A few lines and my fav env is created. #ILike"
27 | >
28 | > **[Jan(ek) Fellien](https://twitter.com/janekf), Microsoft MVP**
29 |
30 | > "[Finally took a look at farmer just now](https://twitter.com/Danthar/status/1263406509951721474).... I should have looked months ago. I mean, I can actually read a farmer template. And it makes sense?!"
31 | >
32 | > **[Arjen Smits](https://twitter.com/Danthar)**
33 |
34 | > "With Farmer we were finally able to organize our Azure infrastructure. No messy names of storageaccounts anymore. Thanks for this great tool!"
35 | >
36 | > **[Tim Forkmann](https://twitter.com/tforkmann), Head of EnergyData at Danpower**
37 |
38 | > "Farmer allows us to keep our infrastructure easy to deploy and maintain. If we use Azure, we use it with Farmer! Thanks for creating it!
39 | >
40 | > **[Stefan Hausotte](https://twitter.com/_secana_), CEO of [Bitfalter](https://www.bitfalter.com)**
41 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | > This article is not a detailed guide on how to create a pull request (PR). See [here](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) to learn more about how to work with pull requests on GitHub.
2 |
3 | The purpose of this article is to illustrate the main checklists you must go through before a PR will be considered for inclusion in Farmer. If you are new to Farmer, F# or GitHub - **don't worry**. The team will be happy to support you getting your feature over the line.
4 |
5 | These are the following checks we'll normally put in place:
6 |
7 | #### 1. Create an issue first!
8 | Except for small pull requests, create an issue to discuss the feature. The last thing we want is for someone to spend hours of their time on a feature only for someone else to have started work on something similar, or for the admins of the project to reject it for whatever reason e.g. does not fit with the project etc. Creating an issue does not take long and will help save time for everyone.
9 | #### 2. Create Documentation
10 | Every PR to Farmer **must** have some documentation with it. If you modify a resource and add a new keyword, it **must** be added to the appropriate docs page.
11 | #### 3. Write Unit Tests
12 | Every PR to Farmer **should** have at least one test associated with it. If no tests are added, you can expect at least a request for one or explanation as to why one is not necessary.
13 | #### 4. Write Release Notes
14 | Every PR to Farmer **must** include an entry to the `RELEASE_NOTES.md` file under the next release. Briefly explain the feature and ideally link to the PR number e.g.
15 | #### 5. Adhere to Coding Standards
16 | Here are some (very basic!) standards for the project:
17 |
18 | * Do not use `yield` - it is no longer necessary in F#.
19 | * Prefer `[ for x in y do ... ]` to `[ for x in y -> ... ]`
20 | * **Never** use `.Value` on `Option` types.
21 |
22 | ##### 5.1 Using Fantomas
23 | We use Fantomas to consistently format F#.
24 |
25 | 1. Install it via: `dotnet tool restore` (only required once).
26 | 2. Run it either:
27 | * Through IDE tooling (e.g. VS Code, Rider or Visual Studio)
28 | * Via the command line `dotnet fantomas src -r`
29 |
30 | If you do not apply Fantomas formatting, your PR will be rejected as this is automatically checked by the build system.
--------------------------------------------------------------------------------
/src/Tests/B2cTenant.fs:
--------------------------------------------------------------------------------
1 | module B2cTenant
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.B2cTenant
6 | open Farmer.Builders
7 | open Newtonsoft.Json.Linq
8 |
9 | let tests =
10 | testList "B2c tenant tests" [
11 | test "B2c tenant should generate the expected arm template" {
12 | let deployment = arm {
13 | location Location.FranceCentral
14 |
15 | add_resources [
16 | b2cTenant {
17 | initial_domain_name "myb2c"
18 | display_name "My B2C tenant"
19 | sku Sku.PremiumP1
20 | country_code "FR"
21 | data_residency B2cDataResidency.Europe
22 | }
23 | ]
24 | }
25 |
26 | let jobj = deployment.Template |> Writer.toJson |> JObject.Parse
27 |
28 | let generatedTemplate = jobj.SelectToken("resources[0]")
29 |
30 | Expect.equal
31 | (generatedTemplate.SelectToken("apiVersion").ToString())
32 | "2021-04-01"
33 | "Invalid ARM template api version"
34 |
35 | Expect.equal
36 | (generatedTemplate.SelectToken("type").ToString())
37 | "Microsoft.AzureActiveDirectory/b2cDirectories"
38 | "Invalid ARM template type"
39 |
40 | Expect.equal
41 | (generatedTemplate.SelectToken("name").ToString())
42 | "myb2c.onmicrosoft.com"
43 | "`name` should match .onmicrosoft.com"
44 |
45 | Expect.equal
46 | (generatedTemplate.SelectToken("properties.createTenantProperties.displayName").ToString())
47 | "My B2C tenant"
48 | "Invalid display name"
49 |
50 | Expect.equal
51 | (generatedTemplate.SelectToken("location").ToString())
52 | "europe"
53 | "`location` should match with the provided `data_residency`"
54 |
55 | Expect.equal
56 | (generatedTemplate.SelectToken("properties.createTenantProperties.countryCode").ToString())
57 | "FR"
58 | "Invalid country code"
59 |
60 | Expect.equal (generatedTemplate.SelectToken("sku.name").ToString()) "PremiumP1" "Invalid sku"
61 | }
62 | ]
--------------------------------------------------------------------------------
/docs/content/quickstarts/template.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "The Farmer .NET Template"
3 | date: 2020-02-04T00:41:51+01:00
4 | draft: false
5 | weight: 4
6 | ---
7 |
8 | Farmer comes with a .NET template that makes getting started easy.
9 |
10 | ### Creating a basic Farmer app
11 | The easiest way to create a Farmer app is to use the Farmer .NET Template.
12 |
13 | ```cmd
14 | dotnet new -i Farmer.Template
15 | dotnet new Farmer
16 | ```
17 |
18 | > You only have to install the template once on your machine!
19 |
20 | This creates a new dotnet application solution and project that looks by default as follows:
21 |
22 | ```fsharp
23 | open Farmer
24 | open Farmer.Builders
25 |
26 | let deployment = arm {
27 | location Location.NorthEurope
28 | }
29 |
30 | printf "Generating ARM template..."
31 | deployment |> Writer.quickWrite "output"
32 | printfn "all done! Template written to output.json"
33 | ```
34 |
35 | From here, you can add resources in the normal manner.
36 |
37 | ### Basic configuration options
38 | You can configure the template using the following optional arguments.
39 |
40 | #### ARM Template filename
41 | The name of the ARM template JSON file e.g. `--armTemplate myTemplate`
42 |
43 | #### Location
44 | The location to create resources in e.g. `--location WestUS`
45 |
46 | ### Deploy Configuration
47 | You can also configure the Farmer template to deploy to Azure out of the box using the `--ci` option. This has two modes of operation:
48 |
49 | #### Azure DevOps deployment
50 | This comes with a ready-made devops YAML file designed for simple CI/CD, using Farmer to generate ARM templates and Azdo to deploy using its own ARM Template deployment process. You should supply the following arguments:
51 |
52 | * **--ci**: Tells the template to create a Farmer app for use with Azure Devops.
53 | * **--azureSubscription**: Set the full name of the Azure Subscription that has been already configured in Azdo that has permission to deploy templates to Azure.
54 | * **--resourceGroup**: Set the name of the resource group that you wish to deploy to.
55 |
56 | #### Direct deployment
57 | If you prefer a deployment process that is not coupled to Azure Devops, you can create a [service principal](../../deployment-guidance/#how-do-i-create-a-service-principal) in Azure and use the generated credentials in Farmer. Farmer will use its own wrapper around the Azure REST API to deploy to Azure, reporting progress to the console.
58 |
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/dedicated-hosts.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Dedicated Hosts"
3 | date: 2022-10-10T22:26:00-04:00
4 | chapter: false
5 | weight: 5
6 | ---
7 |
8 | #### Overview
9 | The `hostGroup` and `host` builders create dedicated hosts in azure and their parent resource, a host group, to efficiently manage a physical host resource in Azure. Dedicated hosts are the same physical servers used in our data centers, provided as a resource. To learn more about dedicated hosts, reference the [Azure Docs](https://learn.microsoft.com/en-us/azure/virtual-machines/dedicated-hosts)
10 |
11 | * Host Group (`Microsoft.Compute/hostGroups`)
12 | * Host (`Microsoft.Compute/hostGroups/hosts`)
13 |
14 | #### Builder Keywords
15 |
16 | | Applies To | Keyword | Purpose |
17 | |-|-|-|
18 | | hostGroup | name | Name of the host group resource |
19 | | hostGroup | add_availability_zone | Assign a zone to the host group. |
20 | | hostGroup| support_automatic_placement | Feature flag for automatic placement of the VMs |
21 | | hostGroup| platform_fault_domain_count | How many fault domains to support, depends on the region. |
22 | | host | name | Name of the host resource |
23 | | host | license_type | The licenses to bring the hosts, i.e. WindowsHybrid, WindowsPerpetual |
24 | | host | auto_replace_on_failure| Feature flag whether to auto replace the host on failure |
25 | | host | sku | name of the sku for the dedicated hosts. Valid sku's vary by subscription, consult the [Dedicated Host documentation](https://learn.microsoft.com/en-us/azure/virtual-machines/dedicated-host-compute-optimized-skus) |
26 | | host | platform_fault_domain | Fault domain to assign the host |
27 | | host | parent_host_group | Name of the host group to assign the hosts to |
28 |
29 | #### Example
30 |
31 | ```fsharp
32 | #r "nuget:Farmer"
33 |
34 | open Farmer
35 | open Farmer.Builders
36 |
37 | arm {
38 | location Location.EastUS
39 |
40 | add_resources
41 | [
42 | hostGroup {
43 | name "myhostgroup"
44 | support_automatic_placement true
45 | add_availability_zone "1"
46 | platform_fault_domain_count 2
47 | }
48 | host {
49 | name "myhost"
50 | parent_host_group (ResourceName "myHostGroup")
51 | platform_fault_domain 2
52 | sku "Fsv2-Type2"
53 | }
54 | ]
55 | }
56 | ```
--------------------------------------------------------------------------------
/src/Tests/CognitiveServices.fs:
--------------------------------------------------------------------------------
1 | module CognitiveServices
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.Builders
6 | open Farmer.CognitiveServices
7 | open Microsoft.Azure.Management.CognitiveServices
8 | open Microsoft.Rest
9 | open System
10 |
11 | let dummyClient =
12 | new CognitiveServicesManagementClient(Uri "http://management.azure.com", TokenCredentials "NotNullOrWhiteSpace")
13 |
14 | let getResourceAtIndex o =
15 | o |> getResourceAtIndex dummyClient.SerializationSettings
16 |
17 | let tests =
18 | testList "Cognitive Services" [
19 | test "Basic Cognitive Services test" {
20 | let service = cognitiveServices {
21 | name "test"
22 | api TextAnalytics
23 | sku S0
24 | add_tags [ "a", "1"; "b", "2" ]
25 | }
26 |
27 | let model: Models.CognitiveServicesAccount = service |> getResourceAtIndex 0
28 |
29 | Expect.equal model.Name "test" "Name is wrong"
30 | Expect.equal model.Kind "TextAnalytics" "Kind is wrong"
31 | Expect.equal model.Sku.Name "S0" "Sku is wrong"
32 |
33 | Expect.sequenceEqual
34 | (model.Tags |> Seq.map (fun x -> x.Key, x.Value))
35 | [ "a", "1"; "b", "2" ]
36 | "Tags are wrong"
37 | }
38 |
39 | test "Key is correctly calculated on a CS instance" {
40 | let service = cognitiveServices { name "test" }
41 |
42 | Expect.equal
43 | service.Key.Owner.Value.ArmExpression.Value
44 | "resourceId('Microsoft.CognitiveServices/accounts', 'test')"
45 | "Owner is wrong"
46 |
47 | Expect.equal
48 | service.Key.Value
49 | "listKeys(resourceId('Microsoft.CognitiveServices/accounts', 'test'), '2017-04-18').key1"
50 | "Key is wrong"
51 | }
52 |
53 | test "Key is correctly calculated with a resource group" {
54 | let key =
55 | CognitiveServices.getKey (
56 | ResourceId.create (Arm.CognitiveServices.accounts, ResourceName "test", "resource group")
57 | )
58 |
59 | Expect.equal
60 | key.Value
61 | "listKeys(resourceId('resource group', 'Microsoft.CognitiveServices/accounts', 'test'), '2017-04-18').key1"
62 | "Key is wrong"
63 | }
64 | ]
--------------------------------------------------------------------------------
/src/Tests/ResourceGroup.fs:
--------------------------------------------------------------------------------
1 | module ResourceGroup
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.Arm.ResourceGroup
6 | open Farmer.Builders
7 |
8 | let tests =
9 | testList "Resource Group" [
10 | test "Creates a resource group" {
11 | let rg = createResourceGroup "myRg" Location.EastUS
12 | Expect.equal rg.Name.Value "myRg" "Incorrect name on resource group"
13 | Expect.equal rg.Location Location.EastUS "Incorrect location on resource group"
14 | Expect.equal rg.Dependencies Set.empty "Resource group should have no dependencies"
15 | Expect.equal rg.Tags Map.empty "Resource group should have no tags"
16 | }
17 | test "Supports multiple nested deployments to the same resource group" {
18 | let nestedRgs =
19 | [ 1..3 ]
20 | |> List.map (fun i ->
21 | resourceGroup {
22 | name "target-rg"
23 | add_resource (storageAccount { name $"stg{i}" })
24 | }
25 | :> IBuilder)
26 |
27 | let rg = resourceGroup {
28 | name "outer-rg"
29 | add_resources nestedRgs
30 | }
31 |
32 | Expect.hasLength rg.Template.Resources 3 "all three resource groups should be added"
33 |
34 | let nestedResources =
35 | rg.Template.Resources
36 | |> List.map (fun x -> x :?> ResourceGroupDeployment)
37 | |> List.collect (fun x -> x.Resources)
38 | |> List.map (fun x -> x.ResourceId.Name.Value)
39 |
40 | Expect.equal nestedResources [ "stg1"; "stg2"; "stg3" ] "all three storage accounts should be nested"
41 | }
42 | test "zip_deploy should be performed when declared in a nested resource" {
43 | let webApp = webApp {
44 | name "webapp"
45 | zip_deploy "deploy"
46 | }
47 |
48 | let oneNestedLevel = resourceGroup { add_resource webApp }
49 | let twoNestedLevels = resourceGroup { add_resource oneNestedLevel }
50 | let threeNestedLevels = arm { add_resource twoNestedLevels }
51 |
52 | Expect.isNonEmpty
53 | (threeNestedLevels :> IDeploymentSource).Deployment.PostDeployTasks
54 | "The zip_deploy should create a post deployment task"
55 | }
56 | ]
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/static-web-app.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Static Web Apps"
3 | date: 2020-02-05T08:53:46+01:00
4 | chapter: false
5 | weight: 18
6 | ---
7 |
8 | #### Overview
9 | The Static Web App builder is used to create [Static Web Apps](https://azure.microsoft.com/en-us/services/app-service/static/). The Static Web App service is a modern web app service that offers streamlined full-stack development from source code to global high availability. You can use it to host static web applications and Azure Functions in a single resource, using GitHub native workflows to build and deploy your application.
10 |
11 | * Static Site (`Microsoft.Web/staticSites`)
12 |
13 | > At the time of writing, Static Web Apps are in public preview. Not all Azure locations support them.
14 |
15 | #### Static Web App Builder Keywords
16 | | Keyword | Purpose |
17 | |-|-|
18 | | name | Sets the name of the static web app. |
19 | | repository | The URI of the GitHub repository containing your static web app. |
20 | | artifact_location | The folder where the built web app is copied to e.g. `build` (optional) |
21 | | api_location | The path containing your Azure Functions (optional) |
22 | | app_location | The path containing your application code (optional) |
23 | | branch | The branch that you which to use for the static web app (optional, defaults to 'master') |
24 | | app_settings | Accepts a list of tuple strings representing key/value pairs for the app setting of the static web app |
25 |
26 | #### Configuration Members
27 | | Name | Purpose |
28 | |-|-|
29 | | RepositoryParameter | Provides the generated name for the repository token parameter name.
30 |
31 | #### Parameters
32 | | Name | Purpose |
33 | |-|-|
34 | | repositorytoken-for-`name` | Provides the Github Personal Access Token (PAT) required to authenticate and create the appropriate Github Action. |
35 |
36 | #### Example
37 |
38 | ```fsharp
39 | open Farmer
40 | open Farmer.Builders
41 |
42 | let myApp = staticWebApp {
43 | name "isaacsstatic"
44 | repository "https://github.com/isaacabraham/staticwebreact"
45 | artifact_location "build"
46 | api_location "api"
47 | app_settings [
48 | "key1", "value1"
49 | "key2", "value2"
50 | ]
51 | }
52 |
53 | let deployment = arm {
54 | location Location.WestEurope
55 | add_resource myApp
56 | }
57 |
58 | deployment
59 | |> Deploy.execute "my-resource-group" [ myApp.RepositoryParameter, "Github personal access token goes here..." ]
60 |
61 | ```
62 |
--------------------------------------------------------------------------------
/src/Tests/LogAnalytics.fs:
--------------------------------------------------------------------------------
1 | module LogAnalytics
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.Arm
6 | open Farmer.Builders
7 | open Farmer.Helpers
8 | open Microsoft.Azure.Management.OperationalInsights
9 | open Microsoft.Azure.Management.OperationalInsights.Models
10 | open Microsoft.Rest
11 | open System
12 |
13 | let dummyClient =
14 | new OperationalInsightsManagementClient(Uri "http://management.azure.com", TokenCredentials "NotNullOrWhiteSpace")
15 |
16 | let asAzureResource (ws: WorkspaceConfig) =
17 | arm { add_resource ws }
18 | |> findAzureResources dummyClient.SerializationSettings
19 | |> List.head
20 | |> fun r ->
21 | r.Validate()
22 | r
23 |
24 | let tests =
25 | testList "Log analytics" [
26 | test "Creates a log analytics workspace" {
27 | let config = logAnalytics {
28 | name "myFarmer"
29 | retention_period 30
30 | enable_query
31 | enable_ingestion
32 | }
33 |
34 | let workspace = asAzureResource config
35 |
36 | Expect.equal workspace.Location "[resourceGroup().location]" "Incorrect Location"
37 | Expect.equal workspace.Name "myFarmer" "Incorrect Name"
38 | Expect.equal workspace.PublicNetworkAccessForIngestion "Enabled" "Incorrect IngestionSupport"
39 | Expect.equal workspace.PublicNetworkAccessForQuery "Enabled" "QuerySupport"
40 | Expect.equal workspace.Sku.Name "PerGB2018" "Incorrect Sku"
41 | Expect.equal workspace.RetentionInDays (Nullable 30) "Incorrect Retention In Days"
42 | }
43 |
44 | test "Ingestion and Query are disabled by default" {
45 | let workspace = logAnalytics { name "" } |> asAzureResource
46 |
47 | Expect.equal workspace.RetentionInDays (Nullable()) "Retention Period should be off by default"
48 | Expect.equal workspace.PublicNetworkAccessForQuery null "Query should be off by default"
49 | Expect.equal workspace.PublicNetworkAccessForIngestion null "Ingestion should be off by default"
50 | }
51 |
52 | test "Can't create log analytics with retention period outside 30 and 730 " {
53 | for days in [ 29; 731 ] do
54 | Expect.throws
55 | (fun _ -> logAnalytics { retention_period days } |> ignore)
56 | (sprintf "Should have thrown for %d" days)
57 | }
58 | ]
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/azure-firewall.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Azure Firewall"
3 | date: 2021-07-07T11:22:17-05:00
4 | chapter: false
5 | weight: 1
6 | ---
7 |
8 | #### Overview
9 |
10 | The Azure Firewall builder (`azureFirewall`) is used to create Azure Firewall instances.
11 |
12 | - Azure Firewall (`Microsoft.Network/azureFirewalls`)
13 |
14 | #### Builder Keywords
15 |
16 | | Resource | Keyword | Purpose |
17 | |---------------|-----------------------------------|--------------------------------------------------------------------------------|
18 | | azureFirewall | name | Sets the name of the azure firewall |
19 | | azureFirewall | sku | Sets the name and tier of the Azure firewall sku |
20 | | azureFirewall | link_to_unmanaged_firewall_policy | Configure the azure firewall to use an existing firewall policy |
21 | | azureFirewall | link_to_firewall_policy | Configure the Azure firewall to use a firewall policy deployed by Farmer |
22 | | azureFirewall | link_to_unmanaged_vhub | Specify the existing virtual hub to which the azure firewall belongs |
23 | | azureFirewall | link_to_vhub | Specify the virtual hub deployed by Farmer to which the azure firewall belongs |
24 | | azureFirewall | public_ip_reservation_count | Specify the number of Public IP addresses associated with the azure firewall |
25 | | azureFirewall | availablity_zones | Specify the availability zones. |
26 | | azureFirewall | pick_zones | Picks availability zones within a region. |
27 | | azureFirewall | depends_on | Specify resources deployed by Farmer the azure firewall depends on |
28 |
29 | ### Example
30 |
31 | ```fsharp
32 | open Farmer
33 | open Farmer.Builders
34 |
35 | let firewall = azureFirewall {
36 | name "farmer_firewall"
37 | sku SkuName.AZFW_Hub SkuTier.Standard
38 | public_ip_reservation_count 2
39 | link_to_unmanaged_vhub (virtualHubs.resourceId "unmanaged-vhub")
40 | }
41 |
42 | let deployment = arm {
43 | location Location.NorthEurope
44 | add_resource firewall
45 | }
46 | ```
47 |
--------------------------------------------------------------------------------
/docs/content/api-overview/resources/operations-management.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Operations Management"
3 | date: 2022-04-29T09:40:00-04:00
4 | weight: 15
5 | chapter: false
6 | ---
7 |
8 | #### Overview
9 | The Operations Management builder is used to create [Solutions](https://docs.microsoft.com/en-us/azure/templates/microsoft.operationsmanagement/solutions?tabs=bicep) for a [Log Analytics Workspace](https://docs.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-workspace-overview).
10 |
11 | #### Builder Keywords
12 |
13 | | Builder | Keyword | Purpose |
14 | |-|-|-|
15 | | omsPlan | name | The name of the plan, which can match the name of the overall Solution. |
16 | | omsPlan | publisher | The publisher of the solution, usually "Microsoft" (the default value). |
17 | | omsPlan | product | The specific solution being created, such as `OMGSGallery/SecurityInsights`. |
18 | | omsProperties | workspace | The Log Analytics workspace this solution uses. |
19 | | omsProperties | add_contained_resource | Adds a resource contained by this solution. |
20 | | omsProperties | add_contained_resources | Adds multiple resources contained by this solution. |
21 | | omsProperties | add_referenced_resource | Adds a resource referenced by this solution. |
22 | | omsProperties | add_referenced_resources | Adds multiple resources referenced by this solution. |
23 | | oms | name | The name of the solution. |
24 | | oms | plan | The `OMSPlan` for the solution. |
25 | | oms | properties | The `OMSProperties` for the solution. |
26 | | oms | add_tag | Add a tag to the solution. |
27 | | oms | add_tags | Add one or more tags to the solution. |
28 |
29 | #### Example
30 |
31 | This example creates an Azure Sentinel solution on a Log Analytics Workspace.
32 |
33 | ```fsharp
34 | open Farmer
35 | open Farmer.Builders
36 |
37 | let sentinelWorkspace = logAnalytics {
38 | name "my-sentinel-workspace"
39 | retention_period 30
40 | enable_query
41 | daily_cap 5
42 | }
43 |
44 | let solutionName = $"SecurityInsights({sentinelWorkspace.Name.Value})"
45 |
46 | let sentinelSolution = oms {
47 | name solutionName
48 | plan (omsPlan {
49 | name solutionName
50 | publisher "Microsoft"
51 | product "OMSGallery/SecurityInsights"
52 | })
53 | properties(omsProperties {
54 | workspace sentinelWorkspace
55 | })
56 | }
57 |
58 | let deployment = arm {
59 | location Location.NorthCentralUS
60 | add_resource sentinelWorkspace
61 | add_resource sentinelSolution
62 | }
63 | ```
64 |
65 |
66 |
--------------------------------------------------------------------------------
/docs/content/about/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "About"
3 | date: 2020-02-04T22:44:07+01:00
4 | weight: 1
5 | ---
6 |
7 | #### About Farmer
8 | Farmer is a .NET domain-specific-language (DSL) for rapidly generating Azure Resource Manager (ARM) templates. Farmer is [commercially supported](../support/), open source and free-to-use.
9 |
10 | For those of you working with Azure today, you may already be aware that one of the most useful features is the ability to generate entire infrastructure architectures as code via ARM Template files. These templates contain a declarative model that allows repeatable deployments and idempotent releases (among other things).
11 |
12 | #### What's wrong with ARM?
13 | Unfortunately, ARM templates have some limitations caused by the fact that they must be authored in a verbose JSON dialect:
14 | * They provide very limited type checking and support, which makes creating discovery and creation of template features difficult.
15 | * Templates need a lot of boilerplate to be created for even relatively simple and common resources.
16 | * It requires "embedded", difficult-to-maintain stringly-typed code in order to achieve what might be trivial in a "proper" programming language, such as references, variables and parameters - or writing elements such as loops.
17 | * The documentation for ARM templates is not always kept up-to-date, so understanding and learning how to properly use them can involve a lot of searching and trial-and-error.
18 |
19 | In other words, whilst working with ARM templates that have already been created is relatively straightforward, the *authoring* of the templates themselves is time-consuming and error-prone.
20 |
21 | Whilst there have been some recent improvements to ARM - including tooling improvements in VS Code through an extension - we think that we can do much better than relying on tooling for a specific IDE, which means using something different than JSON when directly authoring ARM templates.
22 |
23 | #### What does Farmer do to fix this?
24 | Farmer templates are simple .NET Core applications which reference the [Farmer NuGet package](https://www.nuget.org/packages/Farmer/). This package contains a set of *types* that model Azure resources in a strongly-typed and succinct fashion, as well as functionality to create ARM templates from this model - and even deploy directly to Azure.
25 |
26 | #### What can I use Farmer for?
27 | Farmer currently has support for a [large number of common resources](../api-overview/resources) including web apps, sql and storage, with more being added over time.
--------------------------------------------------------------------------------
/src/Tests/LogicApps.fs:
--------------------------------------------------------------------------------
1 | module LogicApps
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.Arm
6 | open Farmer.Builders
7 | open Farmer.Helpers
8 | open Microsoft.Azure.Management.Logic
9 | open Microsoft.Azure.Management.Logic.Models
10 | open Microsoft.Rest
11 | open System
12 | open System.Text.Json
13 |
14 | let dummyClient =
15 | new LogicManagementClient(Uri "http://management.azure.com", TokenCredentials "NotNullOrWhiteSpace")
16 |
17 | let asAzureResource (lac: LogicAppConfig) =
18 | arm { add_resource lac }
19 | |> findAzureResources dummyClient.SerializationSettings
20 | |> List.head
21 | |> fun r ->
22 | r.Validate()
23 | r
24 |
25 | let tests =
26 | testList "Logic Apps" [
27 | test "Creates a logic app workflow" {
28 | let config = logicApp { name "test-logic-app" }
29 | let workflow = asAzureResource config
30 |
31 | Expect.equal workflow.Name "test-logic-app" "Incorrect workflow name"
32 | }
33 | test "Populates a value-based logic app definition" {
34 | // this is the required bare minimum for an empty logic app
35 | // for it to not be set to "null" after parsing
36 | let value =
37 | """
38 | {
39 | "definition": {
40 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
41 | "actions": {},
42 | "contentVersion": "1.0.0.0",
43 | "outputs": {},
44 | "parameters": {},
45 | "triggers": {}
46 | },
47 | "parameters": {}
48 | }
49 | """
50 |
51 | let config = logicApp {
52 | name "test-logic-app"
53 | definition (ValueDefinition value)
54 | }
55 |
56 | let workflow = asAzureResource config
57 |
58 | Expect.isNotNull workflow.Definition "Did not set logic app definition"
59 | }
60 | test "Populates a file-based logic app definition" {
61 | let path = "./test-data/blank-logic-app.json"
62 |
63 | let config = logicApp {
64 | name "test-logic-app"
65 | definition (FileDefinition path)
66 | }
67 |
68 | let workflow = asAzureResource config
69 | Expect.isNotNull workflow.Definition "Did not load definition from file"
70 | }
71 | ]
--------------------------------------------------------------------------------
/src/Farmer/Builders/Builders.NatGateway.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Builders.NatGateway
3 |
4 | open Farmer
5 | open Farmer.Arm.Network
6 | open Farmer.PublicIpAddress
7 |
8 | type NatGatewayConfig = {
9 | Name: ResourceName
10 | IdleTimeout: int
11 | Sku: NatGateway.Sku
12 | Tags: Map
13 | } with
14 |
15 | interface IBuilder with
16 | member this.ResourceId = natGateways.resourceId this.Name
17 |
18 | member this.BuildResources location = [
19 | // Currently generate with a single public IP.
20 | {
21 | PublicIpAddress.Name = ResourceName $"{this.Name.Value}-publicip-1"
22 | AvailabilityZones = NoZone
23 | Location = location
24 | Sku =
25 | match this.Sku with
26 | | Farmer.NatGateway.Sku.Standard -> PublicIpAddress.Sku.Standard
27 | | Farmer.NatGateway.Sku.StandardV2 -> PublicIpAddress.Sku.StandardV2
28 | AllocationMethod = AllocationMethod.Static
29 | AddressVersion = Network.AddressVersion.IPv4
30 | DomainNameLabel = None
31 | Tags = this.Tags
32 | }
33 | {
34 | NatGateway.Name = this.Name
35 | Sku = this.Sku
36 | Location = location
37 | PublicIpAddresses = [
38 | LinkedResource.Managed(publicIPAddresses.resourceId $"{this.Name.Value}-publicip-1")
39 | ]
40 | PublicIpPrefixes = []
41 | IdleTimeout = this.IdleTimeout
42 | Tags = this.Tags
43 | }
44 | ]
45 |
46 | type NatGatewayBuilder() =
47 | member _.Yield _ = {
48 | Name = ResourceName.Empty
49 | IdleTimeout = 4
50 | Sku = NatGateway.Sku.Standard
51 | Tags = Map.empty
52 | }
53 |
54 | []
55 | member _.Name(state: NatGatewayConfig, name: string) = { state with Name = ResourceName name }
56 |
57 | []
58 | member _.SetIdleTimeout(state: NatGatewayConfig, idleTimeout: int) =
59 | if idleTimeout > 120 then
60 | raiseFarmer "Maximum idle timeout is 120 minutes."
61 |
62 | { state with IdleTimeout = idleTimeout }
63 |
64 | []
65 | member _.Sku(state: NatGatewayConfig, sku: NatGateway.Sku) = { state with Sku = sku }
66 |
67 | let natGateway = NatGatewayBuilder()
--------------------------------------------------------------------------------
/docs/content/quickstarts/quickstart-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Your first Farmer template"
3 | date: 2020-02-04T00:41:51+01:00
4 | draft: false
5 | weight: 1
6 | ---
7 |
8 | #### Introduction
9 | In this exercise, you'll:
10 | * create a web application with a fully-configured Application Insights instance
11 | * create an ARM deployment object and assign the web app to it
12 | * generate an ARM template
13 |
14 | #### Creating a Farmer app
15 | Create an F# console application using the .NET SDK and add the Farmer package in an empty directory:
16 |
17 | ```cmd
18 | dotnet new console -lang F#
19 | dotnet add package Farmer
20 | ```
21 |
22 | > Farmer also has a [.NET template](../template/) to get started even more quickly!
23 |
24 | #### Defining a Farmer web application
25 | Open `Program.fs` and delete all the contents.
26 |
27 | > In Farmer, resources are defined using special code blocks that look somewhat json-esque, known as a "builder". In these builders you can quickly and easily configure a resource using special keywords, but unlike json you also have edit-time safety.
28 |
29 | Create a Farmer web application using the `webApp { }` builder:
30 |
31 | ```fsharp
32 | open Farmer
33 | open Farmer.Builders
34 |
35 | let myWebApp = webApp {
36 | name "yourFirstFarmerApp"
37 | }
38 | ```
39 |
40 | Create an ARM template deployment object, before setting the location for the overall resource group and adding the web app into it.
41 |
42 | ```fsharp
43 | let deployment = arm {
44 | location Location.NorthEurope
45 | add_resource myWebApp
46 | }
47 | ```
48 |
49 | #### Generating the ARM template
50 | Now you need to generate the ARM template from the deployment object to an ARM json file.
51 |
52 | Add the following code:
53 |
54 | ```fsharp
55 | deployment
56 | |> Writer.quickWrite "myFirstTemplate"
57 | ```
58 |
59 | Run the application:
60 |
61 | ```cmd
62 | dotnet run
63 | ```
64 |
65 | You should notice that the file `myFirstTemplate.json` has been created.
66 |
67 | The generated ARM template contains the following resources:
68 |
69 | * A web application
70 | * A server farm
71 | * An application insights instance
72 |
73 | The resources will be correctly configured with the appropriate dependencies set.
74 |
75 | #### The full application
76 |
77 | ```fsharp
78 | open Farmer
79 | open Farmer.Builders
80 |
81 | let myWebApp = webApp {
82 | name "yourFirstFarmerApp"
83 | }
84 |
85 | let deployment = arm {
86 | location Location.NorthEurope
87 | add_resource myWebApp
88 | }
89 |
90 | deployment
91 | |> Writer.quickWrite "myFirstTemplate"
92 | ```
93 |
--------------------------------------------------------------------------------
/docs/content/arm-vs-farmer/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Farmer and ARM"
3 | date: 2020-05-23T18:11:25+02:00
4 | draft: false
5 | weight: 4
6 | ---
7 |
8 | | | Farmer | ARM Template |
9 | |-|-:|:-|
10 | | **Core ARM features** |
11 | | Repeatable deployments? | **Yes**, Farmer runs on top of ARM | **Yes** |
12 | | ARM deployment mechanisms? | **All**, plus easy-to-use F# deployment | **All** |
13 | | Variables support? | **Yes**, native support in F# | **Yes** |
14 | | Parameters support? | **Yes**, native support in F# or secure parameters | **Yes** |
15 | | Supported resources? | **All**, including **custom builders for ~50 popular resources** | **All** |
16 | | Declarative model support? | **Yes** | **Yes** |
17 | | Support for all ARM tools? | **Yes**, Farmer runs on top of ARM | **Yes** |
18 | | Linked Template support? | **No** - generally not required. | **Yes** |
19 | | **Authoring** |
20 | | Easy to author? | **Yes** | **No** |
21 | | Easy to read? | **Yes** | **No** |
22 | | Documented? | **Yes**, website and discoverable intellisense | **Limited**, documented but often out-of-date |
23 | | Editor support? | **Yes**, any F# editor including VS Code, VS and Rider | **Limited**, only VS Code has any support |
24 | | **Safety** |
25 | | Type-safe? | **Yes**, full support from the F# compiler and type system | **Limited** through VS Code extension and LSP |
26 | | Validation support? | **Edit-time, run-time, deploy-time** | **Deploy-time and limited edit-time** |
27 | | **Flexibility** |
28 | | Link resources easily? | **Yes** | **Not easily** complex path expressions must be known |
29 | | Compose resources together? | **Yes** | **Not easily** |
30 | | Create multiple resources simultaneously? | **Yes** | **No**, each resource must be defined separately |
31 | | Create resources in several ways? | **Yes**, builders, records, functions or classes | **No**, must use JSON |
32 | | Full programming language? | **Yes**, F# is a simple yet powerful programming language | **No**, JSON with limited functions |
33 | | Imperative model? | **Yes**, F# supports imperative programming | **No**, you must program in a declarative style |
34 | | **Interop and extensibility** |
35 | | Add your own ARM resources? | **Yes**, plug-in model to add new ARM resources | **N/A**
36 | | Create your own combinations of resources? | **Yes** | **No**, each resource must be defined separately |
37 | | Use external libraries? | **Yes**, use any NuGet packages during authoring and full .NET Core | **No**, fixed set of functions |
38 | | Use in .NET applications? | **Yes**, Farmer is a .NET Core library and can be used in-proc | **No**, JSON files |
--------------------------------------------------------------------------------
/src/Tests/IotHub.fs:
--------------------------------------------------------------------------------
1 | module IotHub
2 |
3 | open Expecto
4 | open Farmer
5 | open Farmer.Builders
6 | open Farmer.IotHub
7 | open Microsoft.Azure.Management.DeviceProvisioningServices
8 | open Microsoft.Azure.Management.DeviceProvisioningServices.Models
9 | open Microsoft.Azure.Management.IotHub
10 | open Microsoft.Azure.Management.IotHub.Models
11 | open Microsoft.Rest
12 | open Microsoft.Rest.Serialization
13 | open System
14 |
15 | let iotClient =
16 | new IotHubClient(Uri "http://management.azure.com", TokenCredentials "NotNullOrWhiteSpace")
17 |
18 | let provisioningClient =
19 | new IotHubClient(Uri "http://management.azure.com", TokenCredentials "NotNullOrWhiteSpace")
20 |
21 | let tests =
22 | testList "IOT Hub" [
23 | test "Can create a basic hub" {
24 | let resource =
25 | let hub = iotHub {
26 | name "isaacsuperhub"
27 | sku B1
28 | capacity 2
29 | partition_count 2
30 | retention_days 3
31 | }
32 |
33 | arm { add_resource hub }
34 | |> findAzureResources iotClient.SerializationSettings
35 | |> List.head
36 |
37 | Expect.equal resource.Name "isaacsuperhub" "Hub name does not match"
38 | Expect.equal resource.Sku.Name "B1" "Sku name is incorrect"
39 | Expect.equal resource.Sku.Capacity (Nullable 2L) "Sku capacity is incorrect"
40 |
41 | let events = resource.Properties.EventHubEndpoints.["events"]
42 | Expect.equal events.PartitionCount (Nullable 2) "Partition count is incorrect"
43 | Expect.equal events.RetentionTimeInDays (Nullable 3L) "Retention time is incorrect"
44 | }
45 |
46 | test "Creates a provisioning service" {
47 | let resource =
48 | let hub = iotHub {
49 | name "iothub"
50 | enable_device_provisioning
51 | }
52 |
53 | let deployment = arm { add_resource hub }
54 |
55 | deployment.Template.Resources.[1].JsonModel
56 | |> Serialization.toJson
57 | |> fun json ->
58 | SafeJsonConvert.DeserializeObject(
59 | json,
60 | provisioningClient.SerializationSettings
61 | )
62 |
63 | Expect.equal resource.Sku.Capacity (Nullable 1L) "Sku capacity is incorrect"
64 | Expect.equal resource.Sku.Name "S1" "Sku name capacity is incorrect"
65 | }
66 | ]
--------------------------------------------------------------------------------
/docs/content/api-overview/template-generation.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Generating templates"
3 | date: 2020-02-05T09:13:36+01:00
4 | draft: false
5 | weight: 1
6 | ---
7 |
8 | Farmer supports several ways to "output" ARM templates.
9 |
10 | #### Generating JSON as a string
11 | You can generate an ARM template as a plain string:
12 |
13 | ```fsharp
14 | let json =
15 | deployment.Template
16 | |> Writer.toJson
17 |
18 | // prints out the JSON
19 | printfn "%s" json
20 | ```
21 |
22 | #### Writing to a file
23 | You can write out the ARM template directly to a file, from which you can then deploy to Azure using whichever mechanism you already use e.g. Azure CLI, Powershell, REST API etc.
24 |
25 | ```fsharp
26 | deployment
27 | |> Writer.quickWrite "myTemplate"
28 | ```
29 |
30 | Notice how we use F#'s pipe operator to "pipe" data from the template configuration into json before writing to a file.
31 |
32 | #### Integrated deployment to Azure
33 | You can also turn over deployment of the template directly to Farmer. In this case, it orchestrates commands to the Azure CLI as required.
34 |
35 | ```fsharp
36 | let response =
37 | deployment
38 | |> Deploy.tryExecute "myResourceGroup" Deploy.NoParameters
39 |
40 | match response with
41 | | Ok outputs -> printfn "Success! Outputs: %A" outputs
42 | | Error error -> printfn "Failed! %s" error
43 | ```
44 |
45 | As you can see, the response of calling `tryExecute` is a `Result` object, which is either `Ok`, in which case any outputs returned from the template are made available as a `Map`, or an `Error`, which is the error returned by the Azure CLI. Alternatively, you can call `execute` which will throw an exception rather than return a Result.
46 |
47 | > You must have the Azure CLI installed on your machine in order for Farmer to perform deployments for you.
48 |
49 | #### Authenticating to Azure
50 | Azure CLI stores a login token on your machine, and Farmer will check for this. If you aren't logged in, Farmer will automatically start the interactive Azure CLI login process for you.
51 |
52 | For automated deployments e.g. continuous deployment or through scripts etc., you'll want to use an unattended deployment mode. Some CI systems, such as Azure Devops come with an pre-authenticated Azure CLI terminal from which you can run an application that uses Farmer. Alternatively, you can create a [service principal](../../deployment-guidance#how-do-i-create-a-service-principal), and supply them to the `Deploy.authenticate` function before calling `Deploy.execute`.
53 |
54 | You should use a secure mechanism for storing and supplying the credentials to Farmer. **Do not commit them into source control!**
55 |
--------------------------------------------------------------------------------
/docs/content/tutorials/custom-output.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Custom Output with ARM Expressions"
3 | date: 2021-04-23
4 | draft: false
5 | ---
6 |
7 | #### Introduction
8 |
9 | Many resources have properties that are only set once the resource is created, such as a public IP's address or an ExpressRoute's circuit service key. It is often helpful to have these as output from the deployment so they are available to any downstream automation tasks.
10 |
11 | In this tutorial, you will deploy an ExpressRoute circuit, create a reference to the `serviceKey` property on the newly deployed circuit, and provide that as the ARM deployment output.
12 |
13 | #### Define the ExpressRoute circuit to deploy
14 |
15 | An ExpressRoute circuit provides direct connectivity into Azure over a telecommunication provider's network rather than traversing the Internet or a VPN. Once the circuit is created, the typical flow is to take the circuit's service key to the telecommunications provider so they can enable it for your business connectivity.
16 |
17 | ```fsharp
18 | open Farmer
19 | open Farmer.Builders
20 |
21 | let er = expressRoute {
22 | name "my-test-circuit"
23 | service_provider "Equinix"
24 | peering_location "Frankfurt"
25 | }
26 | ```
27 |
28 | #### Reference the `serviceKey` Property
29 |
30 | ARM templates support expressions that are evaluated when the template is executed by ARM. These have many different capabilities, but in this case, we want to reference a newly deployed resource - the ExpressRoute circuit.
31 |
32 | First, you can use the type and name of the resource to create a `ResourceId`. Then, that `ResourceId` can be used to build a `reference` expression and retrieve a property of the resource.
33 |
34 | ```fsharp
35 | // Build an ARM resourceId type for the circuit.
36 | let erId = ResourceId.create(Arm.Network.expressRouteCircuits, er.Name)
37 |
38 | // Use that ID to build a reference expression and get a property of the referenced resource.
39 | let serviceKeyRef = ArmExpression.create ($"reference({erId.ArmExpression.Value}).serviceKey")
40 | ```
41 |
42 | #### Adding the Deployment Output
43 |
44 | One or more outputs can be added to an `arm` computation expression to generate outputs from the deployment. An output is created using the name for the output an `ArmExpression`, such as `serviceKeyRef` created above.
45 |
46 | ```fsharp
47 | arm {
48 | location Location.WestEurope
49 | add_resource er
50 | output "er-service-key" serviceKeyRef
51 | } |> Writer.quickWrite "custom-output"
52 | ```
53 |
54 | This results in a template with an output named "er-service-key" that will contain the value of the `serviceKey` property on the newly deployed ExpressRoute circuit.
55 |
--------------------------------------------------------------------------------
/src/Farmer/Arm/Insights.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Farmer.Arm.Insights
3 |
4 | open Farmer
5 |
6 | let private createComponents version =
7 | ResourceType("Microsoft.Insights/components", version)
8 |
9 | /// Classic AI instance
10 | let components = createComponents "2014-04-01"
11 | /// Workspace-enabled AI instance
12 | let componentsWorkspace = createComponents "2020-02-02"
13 |
14 | /// The type of AI instance to create.
15 | type InstanceKind =
16 | | Classic
17 | | Workspace of workspace: ResourceId
18 |
19 | member this.ResourceType =
20 | match this with
21 | | Classic -> components
22 | | Workspace _ -> componentsWorkspace
23 |
24 | type Components = {
25 | Name: ResourceName
26 | Location: Location
27 | LinkedWebsite: ResourceName option
28 | DisableIpMasking: bool
29 | SamplingPercentage: int
30 | InstanceKind: InstanceKind
31 | Tags: Map
32 | Dependencies: ResourceId Set
33 | } with
34 |
35 | interface IArmResource with
36 | member this.ResourceId = components.resourceId this.Name
37 |
38 | member this.JsonModel =
39 | let tags =
40 | match this.LinkedWebsite with
41 | | Some linkedWebsite ->
42 | this.Tags.Add(
43 | $"[concat('hidden-link:', resourceGroup().id, '/providers/Microsoft.Web/sites/', '{linkedWebsite.Value}')]",
44 | "Resource"
45 | )
46 | | None -> this.Tags
47 |
48 | {|
49 | this.InstanceKind.ResourceType.Create(this.Name, this.Location, this.Dependencies, tags) with
50 | kind = "web"
51 | properties = {|
52 | name = this.Name.Value
53 | Application_Type = "web"
54 | ApplicationId =
55 | match this.LinkedWebsite with
56 | | Some linkedWebsite -> linkedWebsite.Value
57 | | None -> null
58 | DisableIpMasking = this.DisableIpMasking
59 | SamplingPercentage = this.SamplingPercentage
60 | IngestionMode =
61 | match this.InstanceKind with
62 | | Workspace _ -> "LogAnalytics"
63 | | Classic -> null
64 | WorkspaceResourceId =
65 | match this.InstanceKind with
66 | | Workspace resourceId -> resourceId.Eval()
67 | | Classic -> null
68 | |}
69 | |}
--------------------------------------------------------------------------------