diff --git a/cli/azd/internal/cmd/add/add_configure.go b/cli/azd/internal/cmd/add/add_configure.go index 70742903b5f..8091cf1f915 100644 --- a/cli/azd/internal/cmd/add/add_configure.go +++ b/cli/azd/internal/cmd/add/add_configure.go @@ -44,6 +44,13 @@ func Configure( project.ResourceTypeDbMySql, project.ResourceTypeDbMongo: return fillDatabaseName(ctx, r, console, p) + case project.ResourceTypeDbCosmos: + r, err := fillDatabaseName(ctx, r, console, p) + if err != nil { + return nil, err + } + r.Props = project.CosmosDBProps{} + return r, nil case project.ResourceTypeMessagingEventHubs: return fillEventHubs(ctx, r, console, p) case project.ResourceTypeMessagingServiceBus: diff --git a/cli/azd/internal/cmd/add/add_preview.go b/cli/azd/internal/cmd/add/add_preview.go index 5f61824cc3b..455a12b259f 100644 --- a/cli/azd/internal/cmd/add/add_preview.go +++ b/cli/azd/internal/cmd/add/add_preview.go @@ -77,6 +77,11 @@ func Metadata(r *project.ResourceConfig) resourceMeta { res.UseEnvVars = []string{ "AZURE_OPENAI_ENDPOINT", } + case project.ResourceTypeDbCosmos: + res.AzureResourceType = "Microsoft.DocumentDB/databaseAccounts" + res.UseEnvVars = []string{ + "AZURE_COSMOS_ENDPOINT", + } case project.ResourceTypeMessagingEventHubs: res.AzureResourceType = "Microsoft.EventHub/namespaces" res.UseEnvVars = []string{ diff --git a/cli/azd/internal/scaffold/scaffold_test.go b/cli/azd/internal/scaffold/scaffold_test.go index 2d2df1f5d75..16b136b3332 100644 --- a/cli/azd/internal/scaffold/scaffold_test.go +++ b/cli/azd/internal/scaffold/scaffold_test.go @@ -109,6 +109,9 @@ func TestExecInfra(t *testing.T) { DbCosmosMongo: &DatabaseCosmosMongo{ DatabaseName: "appdb", }, + DbCosmos: &DatabaseCosmos{ + DatabaseName: "cosmos", + }, DbRedis: &DatabaseRedis{}, ServiceBus: &ServiceBus{}, EventHubs: &EventHubs{}, @@ -134,6 +137,9 @@ func TestExecInfra(t *testing.T) { DbPostgres: &DatabaseReference{ DatabaseName: "appdb", }, + DbCosmos: &DatabaseReference{ + DatabaseName: "cosmos", + }, DbMySql: &DatabaseReference{ DatabaseName: "mysqldb", }, @@ -222,6 +228,21 @@ func TestExecInfra(t *testing.T) { }, }, }, + { + "API with Cosmos", + InfraSpec{ + DbCosmos: &DatabaseCosmos{}, + Services: []ServiceSpec{ + { + Name: "api", + Port: 3100, + DbCosmos: &DatabaseReference{ + DatabaseName: "cosmos", + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cli/azd/internal/scaffold/spec.go b/cli/azd/internal/scaffold/spec.go index 72174caba2b..c254e613085 100644 --- a/cli/azd/internal/scaffold/spec.go +++ b/cli/azd/internal/scaffold/spec.go @@ -16,6 +16,7 @@ type InfraSpec struct { DbPostgres *DatabasePostgres DbMySql *DatabaseMysql DbCosmosMongo *DatabaseCosmosMongo + DbCosmos *DatabaseCosmos DbRedis *DatabaseRedis // Key vault @@ -54,6 +55,16 @@ type DatabaseCosmosMongo struct { DatabaseName string } +type DatabaseCosmos struct { + DatabaseName string + Containers []CosmosSqlDatabaseContainer +} + +type CosmosSqlDatabaseContainer struct { + ContainerName string + PartitionKeyPaths []string +} + type DatabaseRedis struct { } @@ -124,6 +135,7 @@ type ServiceSpec struct { DbPostgres *DatabaseReference DbMySql *DatabaseReference DbCosmosMongo *DatabaseReference + DbCosmos *DatabaseReference DbRedis *DatabaseReference StorageAccount *StorageReference diff --git a/cli/azd/pkg/project/resources.go b/cli/azd/pkg/project/resources.go index 6ea05e2c2db..f5222fe9bd1 100644 --- a/cli/azd/pkg/project/resources.go +++ b/cli/azd/pkg/project/resources.go @@ -17,6 +17,7 @@ func AllResourceTypes() []ResourceType { ResourceTypeDbPostgres, ResourceTypeDbMySql, ResourceTypeDbMongo, + ResourceTypeDbCosmos, ResourceTypeHostContainerApp, ResourceTypeOpenAiModel, ResourceTypeMessagingEventHubs, @@ -31,6 +32,7 @@ const ( ResourceTypeDbPostgres ResourceType = "db.postgres" ResourceTypeDbMySql ResourceType = "db.mysql" ResourceTypeDbMongo ResourceType = "db.mongo" + ResourceTypeDbCosmos ResourceType = "db.cosmos" ResourceTypeHostContainerApp ResourceType = "host.containerapp" ResourceTypeOpenAiModel ResourceType = "ai.openai.model" ResourceTypeMessagingEventHubs ResourceType = "messaging.eventhubs" @@ -50,6 +52,8 @@ func (r ResourceType) String() string { return "MySQL" case ResourceTypeDbMongo: return "MongoDB" + case ResourceTypeDbCosmos: + return "CosmosDB" case ResourceTypeHostContainerApp: return "Container App" case ResourceTypeOpenAiModel: @@ -107,6 +111,8 @@ func (r *ResourceConfig) MarshalYAML() (interface{}, error) { errMarshal = marshalRawProps(raw.Props.(AIModelProps)) case ResourceTypeHostContainerApp: errMarshal = marshalRawProps(raw.Props.(ContainerAppProps)) + case ResourceTypeDbCosmos: + errMarshal = marshalRawProps(raw.Props.(CosmosDBProps)) case ResourceTypeMessagingEventHubs: errMarshal = marshalRawProps(raw.Props.(EventHubsProps)) case ResourceTypeMessagingServiceBus: @@ -158,6 +164,12 @@ func (r *ResourceConfig) UnmarshalYAML(value *yaml.Node) error { return err } raw.Props = cap + case ResourceTypeDbCosmos: + cdp := CosmosDBProps{} + if err := unmarshalProps(&cdp); err != nil { + return err + } + raw.Props = cdp case ResourceTypeMessagingEventHubs: ehp := EventHubsProps{} if err := unmarshalProps(&ehp); err != nil { @@ -210,6 +222,15 @@ type AIModelPropsModel struct { Version string `yaml:"version,omitempty"` } +type CosmosDBProps struct { + Containers []CosmosDBContainerProps `yaml:"containers,omitempty"` +} + +type CosmosDBContainerProps struct { + Name string `yaml:"name,omitempty"` + PartitionKeys []string `yaml:"partitionKeys,omitempty"` +} + type ServiceBusProps struct { Queues []string `yaml:"queues,omitempty"` Topics []string `yaml:"topics,omitempty"` diff --git a/cli/azd/pkg/project/scaffold_gen.go b/cli/azd/pkg/project/scaffold_gen.go index 8e3d3ed4acf..ee38b4a56f6 100644 --- a/cli/azd/pkg/project/scaffold_gen.go +++ b/cli/azd/pkg/project/scaffold_gen.go @@ -157,6 +157,19 @@ func infraSpec(projectConfig *ProjectConfig) (*scaffold.InfraSpec, error) { infraSpec.DbCosmosMongo = &scaffold.DatabaseCosmosMongo{ DatabaseName: res.Name, } + case ResourceTypeDbCosmos: + props := res.Props.(CosmosDBProps) + containers := make([]scaffold.CosmosSqlDatabaseContainer, 0) + for _, c := range props.Containers { + containers = append(containers, scaffold.CosmosSqlDatabaseContainer{ + ContainerName: c.Name, + PartitionKeyPaths: c.PartitionKeys, + }) + } + infraSpec.DbCosmos = &scaffold.DatabaseCosmos{ + DatabaseName: res.Name, + Containers: containers, + } case ResourceTypeDbPostgres: infraSpec.DbPostgres = &scaffold.DatabasePostgres{ DatabaseName: res.Name, @@ -329,6 +342,8 @@ func mapHostUses( switch useRes.Type { case ResourceTypeDbMongo: svcSpec.DbCosmosMongo = &scaffold.DatabaseReference{DatabaseName: useRes.Name} + case ResourceTypeDbCosmos: + svcSpec.DbCosmos = &scaffold.DatabaseReference{DatabaseName: useRes.Name} case ResourceTypeDbPostgres: svcSpec.DbPostgres = &scaffold.DatabaseReference{DatabaseName: useRes.Name} case ResourceTypeDbMySql: diff --git a/cli/azd/resources/scaffold/base/abbreviations.json b/cli/azd/resources/scaffold/base/abbreviations.json index dc62141f9da..1533dee5698 100644 --- a/cli/azd/resources/scaffold/base/abbreviations.json +++ b/cli/azd/resources/scaffold/base/abbreviations.json @@ -37,6 +37,7 @@ "devicesProvisioningServices": "provs-", "devicesProvisioningServicesCertificates": "pcert-", "documentDBDatabaseAccounts": "cosmos-", + "documentDBMongoDatabaseAccounts": "cosmon-", "eventGridDomains": "evgd-", "eventGridDomainsTopics": "evgt-", "eventGridEventSubscriptions": "evgs-", diff --git a/cli/azd/resources/scaffold/templates/main.bicept b/cli/azd/resources/scaffold/templates/main.bicept index 19154b60922..7098e77ac41 100644 --- a/cli/azd/resources/scaffold/templates/main.bicept +++ b/cli/azd/resources/scaffold/templates/main.bicept @@ -101,6 +101,9 @@ output AZURE_RESOURCE_REDIS_ID string = resources.outputs.AZURE_RESOURCE_REDIS_I {{- if .DbPostgres}} output AZURE_RESOURCE_{{alphaSnakeUpper .DbPostgres.DatabaseName}}_ID string = resources.outputs.AZURE_RESOURCE_{{alphaSnakeUpper .DbPostgres.DatabaseName}}_ID {{- end}} +{{- if .DbCosmos }} +output AZURE_RESOURCE_{{alphaSnakeUpper .DbCosmos.DatabaseName}}_ID string = resources.outputs.AZURE_RESOURCE_{{alphaSnakeUpper .DbCosmos.DatabaseName}}_ID +{{- end}} {{- if .DbMySql}} output AZURE_RESOURCE_{{alphaSnakeUpper .DbMySql.DatabaseName}}_ID string = resources.outputs.AZURE_RESOURCE_{{alphaSnakeUpper .DbMySql.DatabaseName}}_ID {{- end}} diff --git a/cli/azd/resources/scaffold/templates/resources.bicept b/cli/azd/resources/scaffold/templates/resources.bicept index de76ae930bd..772bb9562a1 100644 --- a/cli/azd/resources/scaffold/templates/resources.bicept +++ b/cli/azd/resources/scaffold/templates/resources.bicept @@ -69,10 +69,10 @@ module containerAppsEnvironment 'br/public:avm/res/app/managed-environment:0.4.5 {{- end}} {{- if .DbCosmosMongo}} -module cosmos 'br/public:avm/res/document-db/database-account:0.8.1' = { - name: 'cosmos' +module cosmosMongo 'br/public:avm/res/document-db/database-account:0.8.1' = { + name: 'cosmosMongo' params: { - name: '${abbrs.documentDBDatabaseAccounts}${resourceToken}' + name: '${abbrs.documentDBMongoDatabaseAccounts}${resourceToken}' location: location tags: tags locations: [ @@ -103,6 +103,58 @@ module cosmos 'br/public:avm/res/document-db/database-account:0.8.1' = { } {{- end}} +{{- if .DbCosmos }} +module cosmos 'br/public:avm/res/document-db/database-account:0.8.1' = { + name: 'cosmos' + params: { + name: '${abbrs.documentDBDatabaseAccounts}${resourceToken}' + tags: tags + location: location + locations: [ + { + failoverPriority: 0 + isZoneRedundant: false + locationName: location + } + ] + networkRestrictions: { + ipRules: [] + virtualNetworkRules: [] + publicNetworkAccess: 'Enabled' + } + sqlDatabases: [ + { + name: '{{ .DbCosmos.DatabaseName }}' + containers: [ + {{- range .DbCosmos.Containers}} + { + name: '{{ .ContainerName }}' + paths: [ + {{- range $path := .PartitionKeyPaths}} + '{{ $path }}' + {{- end}} + ] + } + {{- end}} + ] + } + ] + sqlRoleAssignmentsPrincipalIds: [ + {{- range .Services}} + {{bicepName .Name}}Identity.outputs.principalId + {{- end}} + principalId + ] + sqlRoleDefinitions: [ + { + name: 'service-access-cosmos-sql-role' + } + ] + capabilitiesToAdd: [ 'EnableServerless' ] + } +} +{{- end}} + {{- if .DbPostgres}} var postgresDatabaseName = '{{ .DbPostgres.DatabaseName }}' var postgresDatabaseUser = 'psqladmin' @@ -385,7 +437,7 @@ module {{bicepName .Name}} 'br/public:avm/res/app/container-app:0.8.0' = { { name: 'mongodb-url' identity:{{bicepName .Name}}Identity.outputs.resourceId - keyVaultUrl: cosmos.outputs.exportedSecrets['mongodb-url'].secretUri + keyVaultUrl: cosmosMongo.outputs.exportedSecrets['mongodb-url'].secretUri } {{- end}} {{- if .DbPostgres}} @@ -449,6 +501,12 @@ module {{bicepName .Name}} 'br/public:avm/res/app/container-app:0.8.0' = { secretRef: 'mongodb-url' } {{- end}} + {{- if .DbCosmos}} + { + name: 'AZURE_COSMOS_ENDPOINT' + value: cosmos.outputs.endpoint + } + {{- end}} {{- if .DbPostgres}} { name: 'POSTGRES_HOST' @@ -718,6 +776,9 @@ output AZURE_RESOURCE_{{alphaSnakeUpper .DbPostgres.DatabaseName}}_ID string = ' {{- if .DbMySql}} output AZURE_RESOURCE_{{alphaSnakeUpper .DbMySql.DatabaseName}}_ID string = '${mysqlServer.outputs.resourceId}/databases/{{.DbMySql.DatabaseName}}' {{- end}} +{{- if .DbCosmos }} +output AZURE_RESOURCE_{{alphaSnakeUpper .DbCosmos.DatabaseName}}_ID string = '${cosmos.outputs.resourceId}/sqlDatabases/{{.DbCosmos.DatabaseName}}' +{{- end}} {{- if .StorageAccount }} output AZURE_RESOURCE_STORAGE_ID string = storageAccount.outputs.resourceId {{- end}} diff --git a/schemas/alpha/azure.yaml.json b/schemas/alpha/azure.yaml.json index 9d77013f002..c7e2f43c036 100644 --- a/schemas/alpha/azure.yaml.json +++ b/schemas/alpha/azure.yaml.json @@ -370,6 +370,7 @@ "db.mysql", "db.redis", "db.mongo", + "db.cosmos", "ai.openai.model", "host.containerapp", "messaging.eventhubs", @@ -394,6 +395,7 @@ { "if": { "properties": { "type": { "const": "db.mysql" }}}, "then": { "$ref": "#/definitions/resource"} }, { "if": { "properties": { "type": { "const": "db.redis" }}}, "then": { "$ref": "#/definitions/resource"} }, { "if": { "properties": { "type": { "const": "db.mongo" }}}, "then": { "$ref": "#/definitions/resource"} }, + { "if": { "properties": { "type": { "const": "db.cosmos" }}}, "then": { "$ref": "#/definitions/cosmosDbResource"} }, { "if": { "properties": { "type": { "const": "messaging.eventhubs" }}}, "then": { "$ref": "#/definitions/eventHubsResource" } }, { "if": { "properties": { "type": { "const": "messaging.servicebus" }}}, "then": { "$ref": "#/definitions/serviceBusResource" } }, { "if": { "properties": { "type": { "const": "storage" }}}, "then": { "$ref": "#/definitions/storageAccountResource"} }, @@ -1238,6 +1240,7 @@ "db.redis", "db.mysql", "db.mongo", + "db.cosmos", "host.containerapp", "ai.openai.model", "keyvault" @@ -1328,6 +1331,41 @@ } } }, + "cosmosDbResource": { + "type": "object", + "description": "A deployed, ready-to-use Azure Cosmos DB for NoSQL database.", + "additionalProperties": false, + "properties": { + "type": true, + "uses": true, + "containers": { + "type": "array", + "title": "Containers", + "description": "Containers to be created to store data. Each container stores a collection of items.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "title": "Container name.", + "description": "Required. The name of the container." + }, + "partitionKeys": { + "type": "array", + "title": "Partition keys.", + "description": "Required. The partition key(s) used to distribute data across partitions. The ordering of keys matters. By default, a single partition key '/id' is naturally a great choice for most applications.", + "minLength": 1, + "maxLength": 3, + "items": { + "type": "string" + } + } + } + } + } + } + }, "eventHubsResource": { "type": "object", "description": "An Azure Event Hubs namespace.",