Skip to content

Commit e0b126a

Browse files
authored
Merge pull request #1160 from mike-wade/feature/1097-cosmos-db-gremlin-support
#1097 - Gremlin graph support to CosmosDb
2 parents 77273c7 + e2b51e0 commit e0b126a

File tree

5 files changed

+193
-63
lines changed

5 files changed

+193
-63
lines changed

RELEASE_NOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ Release Notes
77
## 1.9.7
88
- AKS Cluster: support for Sku and Tier. Support for pod subnet in agent pool config. Support for node pool autoscaling
99

10+
## 1.9.7
11+
* CosmosDB: Add support for Gremlin Graphs
12+
1013
## 1.9.6
1114
- Network Interface: Support for adding Network Security Group (NSG) to Network Interface (NIC)
1215

docs/content/api-overview/resources/cosmos-db.md

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,39 @@ The CosmosDb package containers two builders, used to create *databases* and *co
1212
* CosmosDB SQL (`Microsoft.DocumentDB/databaseAccounts/sqlDatabases`)
1313
* CosmosDB MongoDB (`Microsoft.DocumentDB/databaseAccounts/mongodbDatabases`)
1414
* CosmosDB SQL Container (`Microsoft.DocumentDb/databaseAccounts/sqlDatabases/containers`)
15+
* CosmosDB Graph databases (`Microsoft.DocumentDb/databaseAccounts/gremlinDatabases`)
16+
* CosmosDB Graph containers (`Microsoft.DocumentDb/databaseAccounts/gremlinDatabases/graphs`)
1517

16-
> There is currently only support for document databases (the so-called "SQL API"), with support for Gremlin, Table and Cassandra data models planned.
18+
> There is currently support for document databases (the so-called "SQL API") and Gremlin graphs. Support for Table and Cassandra data models planned.
1719
1820
#### Cosmos DB Builder
1921
The CosmosDB builder abstracts the idea of an account and database into one. If you wish to "re-use" an already-created Cosmos DB account, use the `link_to_account` keyword - no account will be created and the database will be attached to the existing one.
2022

21-
| Applies To | Keyword | Purpose |
22-
|-|-|-|
23-
| Database | name | Sets the name of the database. |
24-
| Database | link_to_account | Instructs Farmer to link this database to an existing Cosmos DB account rather than creating a new one. |
25-
| Database | throughput | Sets the throughput with either "provisioned throughput" or "serverless". |
26-
| Database | add_containers | Adds a list of containers to the database. |
27-
| Account | account_name | Sets the name of the CosmosDB account. |
28-
| Account | api (not yet implemented) | Sets the API and data model to use -- currently defaults to "Core (SQL)". |
29-
| Account | enable_public_network_access | Enables public network access for the account. |
23+
| Applies To | Keyword | Purpose |
24+
|-|-------------------------------|-|
25+
| Database | name | Sets the name of the database. |
26+
| Database | link_to_account | Instructs Farmer to link this database to an existing Cosmos DB account rather than creating a new one. |
27+
| Database | throughput | Sets the throughput with either "provisioned throughput" or "serverless". |
28+
| Database | add_containers | Adds a list of containers to the database. |
29+
| Account | account_name | Sets the name of the CosmosDB account. |
30+
| Account | kind | Sets the API and data model to use -- currently defaults to "Core (SQL)". |
31+
| Account | enable_public_network_access | Enables public network access for the account. |
3032
| Account | disable_public_network_access | Disables public network access for the account. |
31-
| Account | consistency_policy | Sets the consistency policy of the database. |
32-
| Account | failover_policy | Sets the failover policy of the database. |
33-
| Account | free_tier | Registers this server with the free pricing tier, if supported and allowed by Azure. |
33+
| Account | consistency_policy | Sets the consistency policy of the database. |
34+
| Account | failover_policy | Sets the failover policy of the database. |
35+
| Account | free_tier | Registers this server with the free pricing tier, if supported and allowed by Azure. |
3436

3537
#### Cosmos Container Builder
3638
The container builder allows you to create and configure a specific container that is attached to a cosmos database.
3739

38-
| Keyword | Purpose |
39-
|-|-|
40-
| name | Sets the name of the container. |
41-
| partition_key | Sets the partition key of the container. |
42-
| add_index | Adds an index to the container. |
43-
| exclude_path | Excludes a path from the container index. |
40+
| Keyword | Purpose |
41+
|-|--------------------------------------------------------------------------------------------|
42+
| name | Sets the name of the container. |
43+
| partition_key | Sets the partition key of the container. |
44+
| add_index | Adds an index to the container. |
45+
| exclude_path | Excludes a path from the container index. |
46+
| gremlin_graph | Marks the container as graph (must be used with an account of `kind DatabaseKind.Gremlin`) |
47+
4448

4549
#### Example
4650
```fsharp
@@ -53,12 +57,14 @@ let myCosmosDb = cosmosDb {
5357
throughput 400<CosmosDb.RU> // or throughput Serverless
5458
failover_policy CosmosDb.NoFailover
5559
consistency_policy (CosmosDb.BoundedStaleness(500, 1000))
60+
//kind DatabaseKind.Gremlin //Create a gremlin enabled account
5661
add_containers [
5762
cosmosContainer {
5863
name "myContainer"
5964
partition_key [ "/id" ] CosmosDb.Hash
6065
add_index "/path" [ CosmosDb.Number, CosmosDb.Hash ]
6166
exclude_path "/excluded/*"
67+
//gremlin_graph //Mark this container to be a graph
6268
}
6369
]
6470
}

src/Farmer/Arm/DocumentDb.fs

Lines changed: 75 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,24 @@ let mongoDatabases =
1616
let databaseAccounts =
1717
ResourceType("Microsoft.DocumentDb/databaseAccounts", "2021-04-15")
1818

19+
let gremlinDatabases =
20+
ResourceType("Microsoft.DocumentDb/databaseAccounts/gremlinDatabases", "2022-05-15")
21+
22+
let graphs =
23+
ResourceType("Microsoft.DocumentDb/databaseAccounts/gremlinDatabases/graphs", "2022-05-15")
24+
1925
type DatabaseKind =
2026
| Document
2127
| Mongo
28+
| Gremlin
2229

2330
module DatabaseAccounts =
24-
module SqlDatabases =
31+
module Containers =
2532
type Container = {
2633
Name: ResourceName
2734
Account: ResourceName
2835
Database: ResourceName
36+
Kind: DatabaseKind
2937
PartitionKey: {|
3038
Paths: string list
3139
Kind: IndexKind
@@ -45,44 +53,56 @@ module DatabaseAccounts =
4553

4654
interface IArmResource with
4755
member this.ResourceId =
48-
containers.resourceId (this.Account / this.Database / this.Name)
56+
match this.Kind with
57+
| Gremlin -> graphs.resourceId (this.Account / this.Database / this.Name)
58+
| _ -> containers.resourceId (this.Account / this.Database / this.Name)
4959

50-
member this.JsonModel = {|
51-
containers.Create(
52-
this.Account / this.Database / this.Name,
53-
dependsOn = [ sqlDatabases.resourceId (this.Account, this.Database) ]
54-
) with
55-
properties = {|
56-
resource = {|
57-
id = this.Name.Value
58-
partitionKey = {|
59-
paths = this.PartitionKey.Paths
60-
kind = string this.PartitionKey.Kind
61-
|}
62-
uniqueKeyPolicy = {|
63-
uniqueKeys =
64-
this.UniqueKeyPolicy.UniqueKeys |> Set.map (fun k -> {| paths = k.Paths |})
65-
|}
66-
indexingPolicy = {|
67-
indexingMode = "consistent"
68-
includedPaths =
69-
this.IndexingPolicy.IncludedPaths
70-
|> List.map (fun p -> {|
71-
path = p.Path
72-
indexes =
73-
p.Indexes
74-
|> List.map (fun (dataType, kind) -> {|
75-
kind = string kind
76-
dataType = dataType.ToString().ToLower()
77-
precision = -1
78-
|})
79-
|})
80-
excludedPaths =
81-
this.IndexingPolicy.ExcludedPaths |> List.map (fun p -> {| path = p |})
60+
member this.JsonModel =
61+
let resourceType =
62+
match this.Kind with
63+
| Gremlin -> graphs
64+
| _ -> containers
65+
66+
{|
67+
resourceType.Create(
68+
this.Account / this.Database / this.Name,
69+
dependsOn = [
70+
match this.Kind with
71+
| Gremlin -> gremlinDatabases.resourceId (this.Account, this.Database)
72+
| _ -> sqlDatabases.resourceId (this.Account, this.Database)
73+
]
74+
) with
75+
properties = {|
76+
resource = {|
77+
id = this.Name.Value
78+
partitionKey = {|
79+
paths = this.PartitionKey.Paths
80+
kind = string this.PartitionKey.Kind
81+
|}
82+
uniqueKeyPolicy = {|
83+
uniqueKeys =
84+
this.UniqueKeyPolicy.UniqueKeys |> Set.map (fun k -> {| paths = k.Paths |})
85+
|}
86+
indexingPolicy = {|
87+
indexingMode = "consistent"
88+
includedPaths =
89+
this.IndexingPolicy.IncludedPaths
90+
|> List.map (fun p -> {|
91+
path = p.Path
92+
indexes =
93+
p.Indexes
94+
|> List.map (fun (dataType, kind) -> {|
95+
kind = string kind
96+
dataType = dataType.ToString().ToLower()
97+
precision = -1
98+
|})
99+
|})
100+
excludedPaths =
101+
this.IndexingPolicy.ExcludedPaths |> List.map (fun p -> {| path = p |})
102+
|}
82103
|}
83104
|}
84-
|}
85-
|}
105+
|}
86106

87107
type SqlDatabase = {
88108
Name: ResourceName
@@ -92,13 +112,17 @@ module DatabaseAccounts =
92112
} with
93113

94114
interface IArmResource with
95-
member this.ResourceId = sqlDatabases.resourceId (this.Account / this.Name)
115+
member this.ResourceId =
116+
match this.Kind with
117+
| Gremlin -> gremlinDatabases.resourceId (this.Account / this.Name)
118+
| _ -> sqlDatabases.resourceId (this.Account / this.Name)
96119

97120
member this.JsonModel =
98121
let resource =
99122
match this.Kind with
100123
| Document -> sqlDatabases
101124
| Mongo -> mongoDatabases
125+
| Gremlin -> gremlinDatabases
102126

103127
{|
104128
resource.Create(this.Account / this.Name, dependsOn = [ databaseAccounts.resourceId this.Account ]) with
@@ -176,6 +200,7 @@ type DatabaseAccount = {
176200
match this.Kind with
177201
| Document -> "GlobalDocumentDB"
178202
| Mongo -> "MongoDB"
203+
| Gremlin -> "GlobalDocumentDB"
179204
properties =
180205
{|
181206
consistencyPolicy = {|
@@ -193,22 +218,30 @@ type DatabaseAccount = {
193218
enableAutomaticFailover = this.EnableAutomaticFailover |> Option.toNullable
194219
enableMultipleWriteLocations = this.EnableMultipleWriteLocations |> Option.toNullable
195220
locations =
196-
match this.FailoverLocations, this.Serverless with
197-
| [], Enabled ->
221+
// Locations has to be specified for the account to be gremlin enabled.
222+
// Otherwise graph database provisioning fails.
223+
match this.FailoverLocations, (this.Serverless = Enabled || this.Kind = Gremlin) with
224+
| [], true ->
198225
box [
199226
{|
200227
locationName = this.Location.ArmValue
201228
|}
202229
]
203-
| [], Disabled -> null
230+
| [], false -> null
204231
| locations, _ -> box locations
205232
publicNetworkAccess = string this.PublicNetworkAccess
206233
enableFreeTier = this.FreeTier
207234
capabilities =
208-
if this.Serverless = Enabled then
209-
box [ {| name = "EnableServerless" |} ]
235+
if this.Serverless = Enabled || this.Kind = Gremlin then
236+
box [
237+
if this.Serverless = Enabled then
238+
{| name = "EnableServerless" |}
239+
if this.Kind = Gremlin then
240+
{| name = "EnableGremlin" |}
241+
]
210242
else
211243
null
244+
212245
|}
213246
|> box
214247
|}

src/Farmer/Builders/Builders.Cosmos.fs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
[<AutoOpen>]
22
module Farmer.Builders.CosmosDb
33

4+
open System
45
open Farmer
6+
open Farmer.Arm
57
open Farmer.CosmosDb
6-
open Farmer.Arm.DocumentDb
78
open DatabaseAccounts
8-
open SqlDatabases
9+
open Containers
910

1011
type KeyType =
1112
| PrimaryKey
@@ -62,6 +63,7 @@ type CosmosDbContainerConfig = {
6263
Indexes: (string * (IndexDataType * IndexKind) list) list
6364
UniqueKeys: Set<string list>
6465
ExcludedPaths: string list
66+
Kind: DatabaseKind
6567
}
6668

6769
type CosmosDbConfig = {
@@ -138,6 +140,7 @@ type CosmosDbConfig = {
138140
Name = container.Name
139141
Account = this.AccountResourceId.Name
140142
Database = this.DbName
143+
Kind = container.Kind
141144
PartitionKey = {|
142145
Paths = fst container.PartitionKey
143146
Kind = snd container.PartitionKey
@@ -160,6 +163,7 @@ type CosmosDbConfig = {
160163
type CosmosDbContainerBuilder() =
161164
member _.Yield _ = {
162165
Name = ResourceName ""
166+
Kind = DatabaseKind.Document
163167
PartitionKey = [], Hash
164168
Indexes = []
165169
UniqueKeys = Set.empty
@@ -186,6 +190,13 @@ type CosmosDbContainerBuilder() =
186190
[<CustomOperation "name">]
187191
member _.Name(state: CosmosDbContainerConfig, name) = { state with Name = ResourceName name }
188192

193+
/// Sets the container kind of the container.
194+
[<CustomOperation "gremlin_graph">]
195+
member _.Graph(state: CosmosDbContainerConfig) = {
196+
state with
197+
Kind = DatabaseKind.Gremlin
198+
}
199+
189200
/// Sets the partition key of the container.
190201
[<CustomOperation "partition_key">]
191202
member _.PartitionKey(state: CosmosDbContainerConfig, partitions, indexKind) = {
@@ -239,6 +250,29 @@ type CosmosDbBuilder() =
239250
Kind = DatabaseKind.Document
240251
}
241252

253+
static member ValidateContainers(state: CosmosDbConfig) =
254+
let validateContainerAndAccountConfig (container: CosmosDbContainerConfig, accountKind: DatabaseKind) =
255+
if container.Kind = accountKind then
256+
Ok container
257+
else
258+
Error $"Container {container.Name.Value} must be of {state.Kind} kind"
259+
260+
state.Containers
261+
|> List.map (fun container -> validateContainerAndAccountConfig (container, state.Kind))
262+
263+
member _.Run state =
264+
let errors =
265+
CosmosDbBuilder.ValidateContainers(state)
266+
|> List.choose (fun r ->
267+
match r with
268+
| Error e -> Some(e)
269+
| Ok _ -> None)
270+
271+
if errors.Length > 0 then
272+
errors |> String.concat Environment.NewLine |> raiseFarmer
273+
274+
state
275+
242276
/// Sets the name of the CosmosDB server.
243277
[<CustomOperation "account_name">]
244278
member _.AccountName(state: CosmosDbConfig, accountName: ResourceName) = {

0 commit comments

Comments
 (0)