An F# DSL for rapidly generating non-complex ARM templates.
- Create non-complex ARM templates through a simple, strongly-typed and pragmatic DSL.
- Create strongly-typed dependencies to resources.
- Just F# - use standard F# code to dynamically create ARM templates quickly and easily.
- Storage
- App Service
- Application Insights
- Cosmos DB
- Azure SQL
- Functions
- Virtual Machines
- Azure Search
Jump to the quickstart or view the API reference.
This is an example bit of Farmer F#:
open Farmer
// Create a storage resource with Premium LRS
let myStorage = storageAccount {
name "mystorage" // set account name
sku Storage.Sku.PremiumLRS // use Premium LRS
}
// Create a web application resource
let myWebApp = webApp {
name "mysuperwebapp" // set web app name
sku WebApp.Sku.S1 // use S1 size
setting "storage_key" myStorage.Key // set an app setting to the storage account key
depends_on myStorage // webapp is dependent on storage
}
// Create the ARM template using those two resources
let location, template = arm {
location Locations.NorthEurope // set location for all resources
add_resource myStorage // include storage into template
add_resource myWebApp // include web app into template
// also output a couple of values generated at deployment-time
output "storage_key" myStorage.Key
output "web_password" myWebApp.PublishingPassword
}
/// Export the template to a file.
template
|> Writer.toJson
|> Writer.toFile "webapp-appinsights.json"
This ends up looking like this, expanding from around 15 lines of more-or-less strongly type code to around 100 lines of more-or-less weakly typed JSON:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"outputs": {
"storage_key": {
"type": "string",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=mystorage;AccountKey=', listKeys('mystorage', '2017-10-01').keys[0].value)]"
},
"web_password": {
"type": "string",
"value": "[list(resourceId('Microsoft.Web/sites/config', 'mysuperwebapp', 'publishingcredentials'), '2014-06-01').properties.publishingPassword]"
}
},
"parameters": {},
"resources": [
{
"apiVersion": "2016-09-01",
"location": "northeurope",
"name": "mysuperwebapp-plan",
"properties": {
"name": "mysuperwebapp-plan",
"perSiteScaling": false,
"reserved": false
},
"sku": {
"name": "S1",
"numberOfWorkers": 1,
"size": "0",
"tier": "Standard"
},
"type": "Microsoft.Web/serverfarms"
},
{
"apiVersion": "2016-08-01",
"dependsOn": [
"mysuperwebapp-plan",
"mystorage",
"mysuperwebapp-ai"
],
"location": "northeurope",
"name": "mysuperwebapp",
"properties": {
"serverFarmId": "mysuperwebapp-plan",
"siteConfig": {
"appSettings": [
{
"name": "storage_key",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=mystorage;AccountKey=', listKeys('mystorage', '2017-10-01').keys[0].value)]"
},
{
"name": "APPINSIGHTS_INSTRUMENTATIONKEY",
"value": "[reference('Microsoft.Insights/components/mysuperwebapp-ai').InstrumentationKey]"
},
{
"name": "APPINSIGHTS_PROFILERFEATURE_VERSION",
"value": "1.0.0"
},
{
"name": "APPINSIGHTS_SNAPSHOTFEATURE_VERSION",
"value": "1.0.0"
},
{
"name": "ApplicationInsightsAgent_EXTENSION_VERSION",
"value": "~2"
},
{
"name": "DiagnosticServices_EXTENSION_VERSION",
"value": "~3"
},
{
"name": "InstrumentationEngine_EXTENSION_VERSION",
"value": "~1"
},
{
"name": "SnapshotDebugger_EXTENSION_VERSION",
"value": "~1"
},
{
"name": "XDT_MicrosoftApplicationInsights_BaseExtensions",
"value": "~1"
},
{
"name": "XDT_MicrosoftApplicationInsights_Mode",
"value": "recommended"
}
]
}
},
"resources": [
{
"apiVersion": "2016-08-01",
"dependsOn": [
"mysuperwebapp"
],
"name": "Microsoft.ApplicationInsights.AzureWebSites",
"properties": {},
"type": "siteextensions"
}
],
"type": "Microsoft.Web/sites"
},
{
"apiVersion": "2014-04-01",
"kind": "web",
"location": "northeurope",
"name": "mysuperwebapp-ai",
"properties": {
"ApplicationId": "mysuperwebapp",
"Application_Type": "web",
"name": "mysuperwebapp-ai"
},
"tags": {
"[concat('hidden-link:', resourceGroup().id, '/providers/Microsoft.Web/sites/', 'mysuperwebapp')]": "Resource",
"displayName": "AppInsightsComponent"
},
"type": "Microsoft.Insights/components"
},
{
"apiVersion": "2018-07-01",
"kind": "StorageV2",
"location": "northeurope",
"name": "mystorage",
"sku": {
"name": "Premium_LRS"
},
"type": "Microsoft.Storage/storageAccounts"
}
]
}
- Clone this repo
- Build the Farmer project.
- Try one of the sample scripts in the Samples folder.
- Alternatively, use the SampleApp to generate your ARM templates from a console app.
Try out the DSL and see what you think.
- Create as many issues as you can for both bugs, discussions and features
- Create suggestions for features and the most important elements you would like to see added
- Create an ARM template using the Farmer sample app.
- Follow the steps here to deploy the generated template into Azure.
- Log any issues or ideas that you find here.
I'm afraid not. F# isn't hard to learn (especially for simple DSLs such as this), and you can easily integrate F# applications as part of a dotnet solution, since F# is a first-class citizen of the dotnet core ecosystem.
No, we're not. Farmer generates ARM templates that can be used just as normal; Farmer can be used simply to make the process of getting started much simpler, or incorporated into your build pipeline as a way to avoid managing difficult-to-manage ARM templates and instead use them as the final part of your build / release pipeline.
No, we're not. Farmer has (at least currently) a specific goal in mind, which is to lower the barrier to entry for creating and working with ARM templates that are non-complex. We're not looking to create a cross-platform DSL to also support things like Terraform etc. or support deployment of code along with infrastructure (or, at least, only to the extent that ARM templates do).
Farmer does support securestring
parameters for e.g. SQL and Virtual Machine passwords - these are automatically generated based on the contents of the template rather than explicitly by yourself. However, we don't currently plan on providing rich support for either parameters or variables for several reasons:
- We want to keep the Farmer codebase simple for maintainers
- We want to kep the Farmer API simple for users
- We want to keep the generated ARM templates as readable as possible
- We feel that instead of trying to embed conditional logic and program flow directly inside ARM templates in JSON, if you wish to parameterise your template that you should use a real programming language to do that: in this case, F#.
You can read more on this issue here
- Open
Program.fs
in theSampleApp
folder. - Create a web application and give it a name. Pick something unique - the name of this web app must be unique across Azure i.e. someone else can't have another web app with the same name!
let myWebApp = webApp {
name "isaacssuperwebapp"
}
- Assign the web app into the existing (empty) arm template definition:
let location, template = arm {
...
add_resource myWebApp // add this line
}
- Run the application.
- Examine the
generated-template.json
file. - Uncomment the last two lines in the application and run it again to deploy the template (see here if you want to learn more about this and what prerequisites are required).
- Once it has deployed, find it in the Azure portal. You will see that three resources were created: the app service, the app service plan that the app service resides in and a linked application insights instance.
- Above the definition of
myWebApp
, create a storage account. The name must be globally unique and between 3-24 alphanumeric lower-case characters:
let myStorage = storageAccount {
name "isaacsuperstorage"
}
- Now add the storage account's connection key to the webapp as an app setting.
let myWebApp = webApp {
...
setting "STORAGE_CONNECTION" myStorage.Key // add this line
}
- Add another entry into the webapp definition that marks the storage account as a dependency. This tells Azure to create the storage account before it creates the web app.
let myWebApp = webApp {
...
depends_on myStorage // add this line
}
- Add it to the body of the
template
definition using the sameresource
keyword as you did withmyWebApp
. - Now regenerate and redeploy the template (don't worry about overwriting or duplicating the existing resources - Azure will simply create the "new" elements as required).
- Check in the portal that the storage account has been created.
- Navigate to the app service and then to the configuration section.
- Observe that the setting
storage_connection
has been created and has the connection string of the storage account already in it.
There are two "recommended" ways of deploying to Azure: either "by hand", in which you use Farmer to generate an ARM template and then deploy that using any number of standard Azure patterns for deploying (one of which is shown below). The alternative is to use Farmer's own simple API for deploying Farmer templates without the hassle of needing to explicitly deal with ARM templates yourself.
Farmer has a simple API for deploying Farmer templates directly to Azure from your development machine, without you having to deal with ARM templates directly; you will need the Azure CLI installed in order to use this.
- Create a Farmer template as normal.
- Pipe the result into the
quickDeploy
function.
let output = arm {
location NorthEurope
//TODO: Assign resources here using the add_resource keyword
}
// Deploy the template to my-resource-group in the default Azure subscription.
output |> Writer.quickDeploy "my-resource-group"
This will launch a simple batch file (help needed to make this x-plat!) which uses the Azure CLI to:
- Sign in to Azure
- Create the resource group
- Deploy the template into that resource group.
- Install the Azure CLI.
- Log in to Azure in the CLI:
az login
. - Create a Resource Group which will store the created Azure services:
az group create --location westus --name MyResourceGroup
. - Deploy the ARM template to the newly-created resource group:
az group deployment create --group MyResourceGroup --template-file generated-arm-template.json
. - Log into the Azure portal to see the results.
(TBD).