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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Release Notes
=============

## Unreleased
* Storage Accounts: Add `AccountKey` member to return just the storage account key and `ConnectionString` member to return the connection string. The existing `Key` member is now obsolete (it incorrectly returned a connection string instead of just the key).

## 1.9.26
* Virtual Machine Scale Sets: Add support for rolling upgrade policy configuration.

Expand Down
2 changes: 1 addition & 1 deletion samples/scripts/container-app.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ let env = containerEnvironment {

replicas 1 10
add_env_variable "QueueName" queueName
add_secret_expression "queueconnectionstring" myStorageAccount.Key
add_secret_expression "queueconnectionstring" myStorageAccount.ConnectionString
}
]
}
Expand Down
2 changes: 1 addition & 1 deletion samples/scripts/safe-template.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let makeSafeApp (environment: string) theLocation storageSku webAppSku =

website_node_default_version "8.1.4"
setting "public_path" "./public"
setting "STORAGE_CONNECTIONSTRING" myStorageAccount.Key
setting "STORAGE_CONNECTIONSTRING" myStorageAccount.ConnectionString
}

arm {
Expand Down
4 changes: 2 additions & 2 deletions samples/scripts/webapp-storage.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ let myWebApp = webApp {
name "mysuperwebapp"
sku WebApp.Sku.S1
app_insights_off
setting "storage_key" myStorage.Key
setting "storage_key" myStorage.ConnectionString
add_allowed_ip_restriction "allow everything" "0.0.0.0/0"
add_denied_ip_restriction "deny" "1.2.3.4/31"
}
Expand All @@ -28,7 +28,7 @@ let deployment = arm {
location Location.NorthEurope
add_resource myStorage
add_resource myWebApp
output "storage_key" myStorage.Key
output "storage_key" myStorage.ConnectionString
output "web_password" myWebApp.PublishingPassword
}

Expand Down
2 changes: 1 addition & 1 deletion src/Farmer/Builders/Builders.ContainerApps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ type ContainerAppBuilder() =
let secretRef = $"scalerule-{name}-connection"

let state: ContainerAppConfig =
this.AddSecretExpression(state, secretRef, storageAccount.Key)
this.AddSecretExpression(state, secretRef, storageAccount.ConnectionString)

let queueRule = {
QueueName = queueName
Expand Down
25 changes: 23 additions & 2 deletions src/Farmer/Builders/Builders.Storage.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,24 @@ open BlobServices
open FileShares

type StorageAccount =
/// Gets an ARM Expression for the account key of any Storage Account.
static member getAccountKey(storageAccount: ResourceId) =
let expr =
$"listKeys({storageAccount.ArmExpression.Value}, '2025-06-01').keys[0].value"

ArmExpression.create (expr, storageAccount)

/// Gets an ARM Expression for the account key of any Storage Account.
static member getAccountKey(storageAccountName: StorageAccountName, ?group) =
let resourceId =
ResourceId.create (storageAccounts, storageAccountName.ResourceName, ?group = group)

StorageAccount.getAccountKey(resourceId).WithOwner(resourceId)

/// Gets an ARM Expression connection string for any Storage Account.
static member getConnectionString(storageAccount: ResourceId) =
let expr =
$"concat('DefaultEndpointsProtocol=https;AccountName={storageAccount.Name.Value};AccountKey=', listKeys({storageAccount.ArmExpression.Value}, '2017-10-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)"
$"concat('DefaultEndpointsProtocol=https;AccountName={storageAccount.Name.Value};AccountKey=', listKeys({storageAccount.ArmExpression.Value}, '2025-06-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)"

ArmExpression.create (expr, storageAccount)

Expand Down Expand Up @@ -88,7 +102,14 @@ type StorageAccountConfig = {
DefaultToOAuthAuthentication: FeatureFlag option
} with

/// Gets the ARM expression path to the key of this storage account.
/// Gets the ARM expression for the primary account key of this storage account.
member this.AccountKey = StorageAccount.getAccountKey (this.Name)

/// Gets the ARM expression for the connection string of this storage account.
member this.ConnectionString = StorageAccount.getConnectionString (this.Name)

/// [Obsolete] Gets the ARM expression for the connection string of this storage account. Use ConnectionString member instead.
[<System.Obsolete("Use ConnectionString instead. This member incorrectly returns a connection string rather than just the key.")>]
member this.Key = StorageAccount.getConnectionString (this.Name)

/// Gets the Primary endpoint for static website (if enabled)
Expand Down
6 changes: 3 additions & 3 deletions src/Tests/ContainerApps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ let fullContainerAppDeployment =
component_type "some.component.type"
version "v1"
add_metadata "meta1" "value1"
add_secret_metadata "meta2" "secret1" storage.Key
add_secret_metadata "meta2" "secret1" storage.ConnectionString
add_scope httpContainerApp
}
]
Expand Down Expand Up @@ -250,7 +250,7 @@ let tests =

Expect.equal
(firstDaprSecret["value"] |> string)
"[concat('DefaultEndpointsProtocol=https;AccountName=storagename;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'storagename'), '2017-10-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
"[concat('DefaultEndpointsProtocol=https;AccountName=storagename;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'storagename'), '2025-06-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
"Incorrect value for secrets[0]"

let scope = daprComponentProperties.SelectToken("scopes[0]")
Expand Down Expand Up @@ -372,7 +372,7 @@ let tests =

Expect.equal
(queueAppSecrets[1]["value"] |> string)
"[concat('DefaultEndpointsProtocol=https;AccountName=storagename;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'storagename'), '2017-10-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
"[concat('DefaultEndpointsProtocol=https;AccountName=storagename;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'storagename'), '2025-06-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
"Incorrect queue app secret"

let queueAppScaleRules =
Expand Down
6 changes: 4 additions & 2 deletions src/Tests/ContainerGroup.fs
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,9 @@ async {
add_volume_mount "script" "/app/src"
command_line ("dotnet fsi /app/src/main.fsx".Split null |> List.ofArray)

env_vars [ EnvVar.createSecureExpression "AZURE_STORAGE_CONNECTION_STRING" storage.Key ]
env_vars [
EnvVar.createSecureExpression "AZURE_STORAGE_CONNECTION_STRING" storage.ConnectionString
]
}
]

Expand Down Expand Up @@ -621,7 +623,7 @@ async {

Expect.equal
(firstEnvVar.["secureValue"] |> string)
"[concat('DefaultEndpointsProtocol=https;AccountName=containerdata1234;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'containerdata1234'), '2017-10-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
"[concat('DefaultEndpointsProtocol=https;AccountName=containerdata1234;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'containerdata1234'), '2025-06-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
"Incorrect env var expression value"
}
test "Container with liveness and readiness probes" {
Expand Down
10 changes: 5 additions & 5 deletions src/Tests/Functions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ let tests =

let wa = functions {
name "testfunc"
setting "storage" sa.Key
setting "storage" sa.ConnectionString
secret_setting "secret"
setting "literal" "value"
link_to_keyvault (ResourceName "testfuncvault")
Expand Down Expand Up @@ -224,7 +224,7 @@ let tests =
"Incorrect secret dependencies"

Expect.equal secrets.[1].Name.Value "testfuncvault/storage" "Incorrect secret name"
Expect.equal secrets.[1].Value (ExpressionSecret sa.Key) "Incorrect secret value"
Expect.equal secrets.[1].Value (ExpressionSecret sa.ConnectionString) "Incorrect secret value"

Expect.sequenceEqual
secrets.[1].Dependencies
Expand Down Expand Up @@ -622,7 +622,7 @@ let tests =
{|
name = "AzureWebJobsDashboard"
value =
"[concat('DefaultEndpointsProtocol=https;AccountName=accountName;AccountKey=', listKeys(resourceId('shared-group', 'Microsoft.Storage/storageAccounts', 'accountName'), '2017-10-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
"[concat('DefaultEndpointsProtocol=https;AccountName=accountName;AccountKey=', listKeys(resourceId('shared-group', 'Microsoft.Storage/storageAccounts', 'accountName'), '2025-06-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
|}
"Invalid value for AzureWebJobsDashboard"

Expand Down Expand Up @@ -663,15 +663,15 @@ let tests =
functions {
name "test"
connection_string "a"
connection_string ("b", sa.Key)
connection_string ("b", sa.ConnectionString)
}
|> getResources

resources |> getResource<Web.Site> |> List.head

let expected = [
"a", (ParameterSetting(SecureParameter "a"), Custom)
"b", (ExpressionSetting sa.Key, Custom)
"b", (ExpressionSetting sa.ConnectionString, Custom)
]

let parameters = wa :> IParameters
Expand Down
8 changes: 6 additions & 2 deletions src/Tests/KeyVault.fs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,12 @@ let tests =
"Invalid value of parameter secret"

let sa = storageAccount { name "storage" }
let expressionSecret = SecretConfig.create ("test", sa.Key)
Expect.equal expressionSecret.Value (ExpressionSecret sa.Key) "Invalid value of expression secret"
let expressionSecret = SecretConfig.create ("test", sa.ConnectionString)

Expect.equal
expressionSecret.Value
(ExpressionSecret sa.ConnectionString)
"Invalid value of expression secret"

Expect.sequenceEqual
expressionSecret.Dependencies
Expand Down
24 changes: 22 additions & 2 deletions src/Tests/Storage.fs
Original file line number Diff line number Diff line change
Expand Up @@ -410,12 +410,12 @@ let tests =
StorageAccount.getConnectionString (StorageAccountName.Create("account").OkValue, "rg")

Expect.equal
"concat('DefaultEndpointsProtocol=https;AccountName=account;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'account'), '2017-10-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)"
"concat('DefaultEndpointsProtocol=https;AccountName=account;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'account'), '2025-06-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)"
strongConn.Value
"Strong connection string"

Expect.equal
"concat('DefaultEndpointsProtocol=https;AccountName=account;AccountKey=', listKeys(resourceId('rg', 'Microsoft.Storage/storageAccounts', 'account'), '2017-10-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)"
"concat('DefaultEndpointsProtocol=https;AccountName=account;AccountKey=', listKeys(resourceId('rg', 'Microsoft.Storage/storageAccounts', 'account'), '2025-06-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)"
rgConn.Value
"Complex connection string"
}
Expand Down Expand Up @@ -958,4 +958,24 @@ let tests =
"false"
"default to OAuth should be disabled"
}
test "AccountKey returns just the storage account key" {
let account = storageAccount { name "account" }

let accountKeyExpression = account.AccountKey.Value

Expect.equal
accountKeyExpression
"listKeys(resourceId('Microsoft.Storage/storageAccounts', 'account'), '2025-06-01').keys[0].value"
"AccountKey should return only the key"
}
test "ConnectionString returns the full connection string" {
let account = storageAccount { name "account" }

let connectionStringExpression = account.ConnectionString.Value

Expect.equal
connectionStringExpression
"concat('DefaultEndpointsProtocol=https;AccountName=account;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'account'), '2025-06-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)"
"ConnectionString should return the full connection string"
}
]
16 changes: 8 additions & 8 deletions src/Tests/WebApp.fs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ let tests =
webApp {
name "test"
connection_string "a"
connection_string ("b", sa.Key)
connection_string ("b", sa.ConnectionString)
connection_string ("c", ArmExpression.create ("c"), ConnectionStringKind.SQLAzure)
}
|> getResources
Expand All @@ -219,7 +219,7 @@ let tests =

let expected = [
"a", (ParameterSetting(SecureParameter "a"), ConnectionStringKind.Custom)
"b", (ExpressionSetting sa.Key, ConnectionStringKind.Custom)
"b", (ExpressionSetting sa.ConnectionString, ConnectionStringKind.Custom)
"c", (ExpressionSetting(ArmExpression.create ("c")), ConnectionStringKind.SQLAzure)
]

Expand Down Expand Up @@ -301,7 +301,7 @@ let tests =

let wa = webApp {
name "testweb"
setting "storage" sa.Key
setting "storage" sa.ConnectionString
setting "conn" (sql.ConnectionString "thedb")
setting "bad" (ArmExpression.literal "ignore_me")
}
Expand All @@ -322,7 +322,7 @@ let tests =

let wa = webApp {
name "testweb"
setting "storage" sa.Key
setting "storage" sa.ConnectionString
}

let wa = wa |> getResources |> getResource<Web.Site> |> List.head
Expand All @@ -338,7 +338,7 @@ let tests =

let wa = webApp {
name "testweb"
setting "astorage" sa.Key
setting "astorage" sa.ConnectionString
secret_setting "bsecret"
secret_setting "csection:secret"
secret_setting "dmy_secret"
Expand Down Expand Up @@ -389,7 +389,7 @@ let tests =
Expect.hasLength secrets 4 "Incorrect number of KV secrets"

Expect.equal secrets.[0].Name.Value "testwebvault/astorage" "Incorrect secret name"
Expect.equal secrets.[0].Value (ExpressionSecret sa.Key) "Incorrect secret value"
Expect.equal secrets.[0].Value (ExpressionSecret sa.ConnectionString) "Incorrect secret value"

Expect.sequenceEqual
secrets.[0].Dependencies
Expand Down Expand Up @@ -424,7 +424,7 @@ let tests =

let wa = webApp {
name "testweb"
setting "storage" sa.Key
setting "storage" sa.ConnectionString
secret_setting "secret"
setting "literal" "value"
link_to_keyvault (ResourceName "testwebvault")
Expand Down Expand Up @@ -465,7 +465,7 @@ let tests =
"Incorrect secret dependencies"

Expect.equal secrets.[1].Name.Value "testwebvault/storage" "Incorrect secret name"
Expect.equal secrets.[1].Value (ExpressionSecret sa.Key) "Incorrect secret value"
Expect.equal secrets.[1].Value (ExpressionSecret sa.ConnectionString) "Incorrect secret value"

Expect.sequenceEqual
secrets.[1].Dependencies
Expand Down
10 changes: 5 additions & 5 deletions src/Tests/test-data/lots-of-resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,11 @@
},
{
"name": "AzureWebJobsDashboard",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=farmerfuncs1979storage;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'farmerfuncs1979storage'), '2017-10-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=farmerfuncs1979storage;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'farmerfuncs1979storage'), '2025-06-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
},
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=farmerfuncs1979storage;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'farmerfuncs1979storage'), '2017-10-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=farmerfuncs1979storage;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'farmerfuncs1979storage'), '2025-06-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
Expand All @@ -291,7 +291,7 @@
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=farmerfuncs1979storage;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'farmerfuncs1979storage'), '2017-10-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=farmerfuncs1979storage;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'farmerfuncs1979storage'), '2025-06-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
},
{
"name": "WEBSITE_CONTENTSHARE",
Expand Down Expand Up @@ -811,7 +811,7 @@
"appSettings": [
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=dockerfuncstorage;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'dockerfuncstorage'), '2017-10-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=dockerfuncstorage;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'dockerfuncstorage'), '2025-06-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
},
{
"name": "DOCKER_REGISTRY_SERVER_PASSWORD",
Expand All @@ -835,7 +835,7 @@
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=dockerfuncstorage;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'dockerfuncstorage'), '2017-10-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=dockerfuncstorage;AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', 'dockerfuncstorage'), '2025-06-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
},
{
"name": "WEBSITE_CONTENTSHARE",
Expand Down