Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add support for Immutable Storage on container #1144

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
5 changes: 4 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ insert_final_newline = false

[*.{fs,fsx}]
fsharp_multiline_bracket_style = stroustrup
fsharp_newline_before_multiline_computation_expression = false
fsharp_newline_before_multiline_computation_expression = false

[*.json]
indent_size = 2
6 changes: 3 additions & 3 deletions Farmer.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29201.188
# Visual Studio Version 17
VisualStudioVersion = 17.12.35417.141
MinimumVisualStudioVersion = 10.0.40219.1
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Farmer", "src\Farmer\Farmer.fsproj", "{CB0287CC-AD12-427C-866B-5F236C29B0A2}"
EndProject
Expand Down Expand Up @@ -44,8 +44,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{C399
samples\scripts\sqlserver.fsx = samples\scripts\sqlserver.fsx
samples\scripts\storage.fsx = samples\scripts\storage.fsx
samples\scripts\template.json = samples\scripts\template.json
samples\scripts\vm.fsx = samples\scripts\vm.fsx
samples\scripts\vm-spot-instance.fsx = samples\scripts\vm-spot-instance.fsx
samples\scripts\vm.fsx = samples\scripts\vm.fsx
samples\scripts\vnet-gateway.fsx = samples\scripts\vnet-gateway.fsx
samples\scripts\vnet-hub-and-spoke.fsx = samples\scripts\vnet-hub-and-spoke.fsx
samples\scripts\vnet.fsx = samples\scripts\vnet.fsx
Expand Down
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Release Notes
## 1.9.5
* Fix an issue with Access Policies that allows non-string sequences to be supplied for user / group lookups.
* Virtual Network: Specify the route table for a subnet.
* Storage: Add BLOB `immutableStorageWithVersioning`
* Storage: Add BLOB `encryption.requireInfrastructureEncryption`
* Storage: Add BLOB container immutability policies support

## 1.9.4
* Network Security Groups: Use a common protocol in security rules with multiple sources. Defaults to Any if sources use different protocols.
Expand Down
6 changes: 6 additions & 0 deletions docs/content/api-overview/resources/storage-account.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ let storage = storageAccount {
add_public_container "mypubliccontainer"
add_private_container "myprivatecontainer"
add_blob_container "myblobcontainer"
add_blob_container
"myblobcontainerwithimmutabilitypolicies"
(blobContainerImmutabilityPolicies {
allow_protected_append_writes AllAppendAllowed
immutability_period_since_creation (365<Days> * 5)
})
add_file_share "share1"
add_file_share_with_quota "share2" 1024<Gb>
add_queue "myqueue"
Expand Down
2 changes: 1 addition & 1 deletion samples/scripts/loadbalancer.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let (lb: LoadBalancer) = {
{|
Name = ResourceName "LoadBalancerFrontend"
AddressVersion = Network.AddressVersion.IPv4
PublicIp = Some (publicIPAddresses.resourceId "lb-test-pip")
PublicIp = Some(publicIPAddresses.resourceId "lb-test-pip")
PrivateIpAllocationMethod = PrivateIpAddress.DynamicPrivateIp
Subnet = None
|}
Expand Down
200 changes: 141 additions & 59 deletions src/Farmer/Arm/Storage.fs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
[<AutoOpen>]
module Farmer.Arm.Storage

open System
open System.Runtime.CompilerServices
open Farmer
open Farmer.Storage

let storageAccounts =
ResourceType("Microsoft.Storage/storageAccounts", "2022-05-01")
ResourceType("Microsoft.Storage/storageAccounts", "2023-05-01")

let blobServices =
ResourceType("Microsoft.Storage/storageAccounts/blobServices", "2019-06-01")
ResourceType("Microsoft.Storage/storageAccounts/blobServices", "2023-05-01")

let containers =
ResourceType("Microsoft.Storage/storageAccounts/blobServices/containers", "2018-03-01-preview")
ResourceType("Microsoft.Storage/storageAccounts/blobServices/containers", "2023-05-01")

let immutabilityPolicies =
ResourceType("Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies", "2023-05-01")

let fileServices =
ResourceType("Microsoft.Storage/storageAccounts/fileServices", "2019-06-01")
Expand Down Expand Up @@ -39,6 +44,15 @@ let roleAssignments =

type Metadata = Map<string, string>

type ImmutabilityPolicyState =
| Unlocked
| Locked

member this.ArmValue =
match this with
| Unlocked -> "Unlocked"
| Locked -> "Locked"

[<RequireQualifiedAccess>]
type NetworkRuleSetBypass =
| None
Expand Down Expand Up @@ -98,21 +112,33 @@ type StorageAccount = {
Location: Location
Sku: Sku
Dependencies: ResourceId list
EnableHierarchicalNamespace: bool option
NetworkAcls: NetworkRuleSet option
StaticWebsite:
{|
IndexPage: string
ErrorPage: string option
ContentPath: string
|} option
MinTlsVersion: TlsVersion option
SupportsHttpsTrafficOnly: FeatureFlag option
DnsZoneType: string option
EnableHierarchicalNamespace: bool option
DefaultToOAuthAuthentication: FeatureFlag option
DisablePublicNetworkAccess: FeatureFlag option
DisableBlobPublicAccess: FeatureFlag option
DisableSharedKeyAccess: FeatureFlag option
DefaultToOAuthAuthentication: FeatureFlag option
DnsZoneType: string option
ImmutableStorageWithVersioning:
{|
Enable: bool option
ImmutabilityPolicy:
{|
AllowProtectedAppendWrites: bool option
ImmutabilityPeriodSinceCreation: int<Days> option
State: ImmutabilityPolicyState option
|} option
|} option
MinTlsVersion: TlsVersion option
NetworkAcls: NetworkRuleSet option
/// <remarks>Azure default is false</remarks>
RequireInfrastructureEncryption: bool option
SupportsHttpsTrafficOnly: FeatureFlag option
Tags: Map<string, string>
} with

Expand Down Expand Up @@ -149,15 +175,13 @@ type StorageAccount = {
| Blobs _ -> "BlobStorage"
| Files _ -> "FileStorage"
| BlockBlobs _ -> "BlockBlobStorage"
extendedLocation = None // TODO:
identity = None // TODO: user assigned identity
properties = {|
isHnsEnabled = this.EnableHierarchicalNamespace |> Option.toNullable
accessTier =
match this.Sku with
| Blobs(_, Some tier)
| GeneralPurpose(V2(_, Some tier)) ->
match tier with
| Hot -> "Hot"
| Cool -> "Cool"
| GeneralPurpose(V2(_, Some tier)) -> tier.ArmValue
| _ -> null
networkAcls =
this.NetworkAcls
Expand All @@ -182,41 +206,33 @@ type StorageAccount = {
defaultAction = networkRuleSet.DefaultAction.ArmValue
|})
|> Option.defaultValue Unchecked.defaultof<_>
minimumTlsVersion =
match this.MinTlsVersion with
| Some Tls10 -> "TLS1_0"
| Some Tls11 -> "TLS1_1"
| Some Tls12 -> "TLS1_2"
| None -> null
supportsHttpsTrafficOnly =
match this.SupportsHttpsTrafficOnly with
| Some FeatureFlag.Disabled -> "false"
| Some FeatureFlag.Enabled -> "true"
| None -> null
dnsEndpointType =
match this.DnsZoneType with
| Some s -> s
| None -> null
publicNetworkAccess =
match this.DisablePublicNetworkAccess with
| Some FeatureFlag.Disabled -> "Enabled"
| Some FeatureFlag.Enabled -> "Disabled"
| None -> null
allowBlobPublicAccess =
match this.DisableBlobPublicAccess with
| Some FeatureFlag.Disabled -> "true"
| Some FeatureFlag.Enabled -> "false"
| None -> null
allowSharedKeyAccess =
match this.DisableSharedKeyAccess with
| Some FeatureFlag.Disabled -> "true"
| Some FeatureFlag.Enabled -> "false"
| None -> null
defaultToOAuthAuthentication =
match this.DefaultToOAuthAuthentication with
| Some FeatureFlag.Disabled -> "false"
| Some FeatureFlag.Enabled -> "true"
| None -> null
allowBlobPublicAccess = this.DisableBlobPublicAccess.AsInvertedBoolean()
allowSharedKeyAccess = this.DisableSharedKeyAccess.AsInvertedBoolean()
defaultToOAuthAuthentication = this.DefaultToOAuthAuthentication.AsBoolean()
dnsEndpointType = this.DnsZoneType |> Option.toObj
encryption =
this.RequireInfrastructureEncryption
|> Option.map (fun _ -> {|
requireInfrastructureEncryption = this.RequireInfrastructureEncryption
|})
immutableStorageWithVersioning =
this.ImmutableStorageWithVersioning
|> Option.map (fun immutableStorage -> {|
enable = immutableStorage.Enable |> Option.toNullable
policy =
immutableStorage.ImmutabilityPolicy
|> Option.map (fun immutableStorage -> {|
allowProtectedAppendWrites =
immutableStorage.AllowProtectedAppendWrites |> Option.toNullable
immutabilityPeriodSinceCreationInDays =
immutableStorage.ImmutabilityPeriodSinceCreation |> Option.toNullable
state = immutableStorage.State |> Option.map _.ArmValue |> Option.toObj
|})
|})
isHnsEnabled = this.EnableHierarchicalNamespace |> Option.toNullable
minimumTlsVersion = this.MinTlsVersion.ArmValue()
publicNetworkAccess = this.DisablePublicNetworkAccess.ArmInvertedValue()
supportsHttpsTrafficOnly = this.SupportsHttpsTrafficOnly.AsBoolean()
xperiandri marked this conversation as resolved.
Show resolved Hide resolved
|}
|}

Expand Down Expand Up @@ -350,26 +366,92 @@ module BlobServices =
Name: StorageResourceName
StorageAccount: ResourceName
Accessibility: StorageContainerAccess
Metadata: Metadata option
} with

member this.ResourceName = this.StorageAccount / "default" / this.Name.ResourceName

interface IArmResource with
member this.ResourceId =
containers.resourceId (this.StorageAccount / "default" / this.Name.ResourceName)
member this.ResourceId = containers.resourceId this.ResourceName

member this.JsonModel = {|
containers.Create(
this.StorageAccount / "default" / this.Name.ResourceName,
dependsOn = [ storageAccounts.resourceId this.StorageAccount ]
containers.Create(this.ResourceName, dependsOn = [ storageAccounts.resourceId this.StorageAccount ]) with
properties = {|
publicAccess = this.Accessibility.ArmValue
metadata = this.Metadata |> Option.defaultValue Unchecked.defaultof<_>
|}
|}

type AllowProtectedAppendWrites =
| NoAppendAllowed
/// When enabled, new blocks can be written to both 'Append and Bock Blobs' while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted.
| AllAppendAllowed
/// When enabled, new blocks can be written to an append blob while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted.
| AppendBlobOnlyAppendAllowed

member internal this.AllowProtectedAppendWrites =
match this with
| NoAppendAllowed -> Nullable()
| AllAppendAllowed -> Nullable()
| AppendBlobOnlyAppendAllowed -> Nullable(true)

member internal this.AllowProtectedAppendWritesAll =
match this with
| NoAppendAllowed -> Nullable()
| AllAppendAllowed -> Nullable(true)
| AppendBlobOnlyAppendAllowed -> Nullable()

[<AbstractClass; Sealed; Extension>]
type AllowProtectedAppendWritesExtensions =

[<Extension>]
static member AllowProtectedAppendWrites(this: AllowProtectedAppendWrites option) =
match this with
| Some value -> value.AllowProtectedAppendWrites
| None -> Nullable()

[<Extension>]
static member AllowProtectedAppendWritesAll(this: AllowProtectedAppendWrites option) =
match this with
| Some value -> value.AllowProtectedAppendWritesAll
| None -> Nullable()

module BlobContainers =
type ImmutabilityPolicies = {
StorageAccount: ResourceName
Container: StorageResourceName
/// This property can only be changed for unlocked time-based retention policies. This property cannot be changed with ExtendImmutabilityPolicy API.
AllowProtectedAppendWrites: AllowProtectedAppendWrites option
/// The immutability period for the blobs in the container since the policy creation, in days.
ImmutabilityPeriodSinceCreation: int<Days> option
} with

member _.Name = ResourceName "default"

member this.ResourceName =
this.StorageAccount / "default" / this.Container.ResourceName / this.Name

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

member this.JsonModel = {|
immutabilityPolicies.Create(
this.ResourceName,
dependsOn = [
containers.resourceId (this.StorageAccount / "default" / this.Container.ResourceName)
]
) with
properties = {|
publicAccess =
match this.Accessibility with
| Private -> "None"
| Container -> "Container"
| Blob -> "Blob"
immutabilityPeriodSinceCreationInDays =
this.ImmutabilityPeriodSinceCreation |> Option.toNullable
// The 'allowProtectedAppendWrites' and 'allowProtectedAppendWritesAll' properties are mutually exclusive
allowProtectedAppendWrites = this.AllowProtectedAppendWrites.AllowProtectedAppendWrites()
allowProtectedAppendWritesAll = this.AllowProtectedAppendWrites.AllowProtectedAppendWritesAll()
|}
|}



module FileShares =
type FileShare = {
Name: StorageResourceName
Expand Down
2 changes: 2 additions & 0 deletions src/Farmer/Builders/Builders.Functions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ type FunctionsConfig = {
DisableBlobPublicAccess = None
DisableSharedKeyAccess = None
DefaultToOAuthAuthentication = None
ImmutableStorageWithVersioning = None
RequireInfrastructureEncryption = None
}
| _ -> ()

Expand Down
Loading