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

cosmosdb conn string api #3213

Merged
merged 36 commits into from Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0ddff66
add support for cosmosdb database
hemarina Jan 11, 2024
57dc7e8
bicept files
hemarina Jan 11, 2024
5624664
add range in databases
hemarina Jan 11, 2024
f1440f6
update cname
hemarina Jan 11, 2024
593f7e7
update name
hemarina Jan 12, 2024
7c2ed3b
comment
hemarina Jan 12, 2024
8da53f0
fix bug on endpoint missing
hemarina Jan 12, 2024
74d1dae
add cosmosdb sql db
hemarina Jan 12, 2024
3c351e6
add sql contribute role
hemarina Jan 12, 2024
6992355
add keyvault, connection string
hemarina Jan 18, 2024
8c291fc
Add connection string on ACA
hemarina Jan 18, 2024
9f8ff1f
testing
hemarina Jan 18, 2024
11121cb
testing
hemarina Jan 18, 2024
a4c88fd
revert
hemarina Jan 18, 2024
eb7480f
put it back
hemarina Jan 19, 2024
f215259
key vault
hemarina Jan 19, 2024
9209a4b
fix bug
hemarina Jan 19, 2024
51a39c8
remove space
hemarina Jan 20, 2024
ceb748b
Merge branch 'main' of https://github.com/Azure/azure-dev into cosmosdb
hemarina Jan 20, 2024
9a742b6
add small change
hemarina Jan 20, 2024
3ba604d
pull connection string from cosmos service using arm client
vhvb1989 Jan 21, 2024
e608146
snap
vhvb1989 Jan 22, 2024
4a6f7aa
test fix
vhvb1989 Jan 22, 2024
18241fc
handle db by getting parent account
vhvb1989 Jan 22, 2024
83c9aa2
Merge branch 'main' of github.com:Azure/azure-dev into cosmosdb-conn-…
vhvb1989 Jan 23, 2024
4428c04
use appcontainer secrets and ref
vhvb1989 Jan 23, 2024
cf5eb6a
lint
vhvb1989 Jan 23, 2024
73463ef
test
vhvb1989 Jan 23, 2024
50ba991
remove duplicated env var due to ref
vhvb1989 Jan 23, 2024
048ff1e
update snap
vhvb1989 Jan 23, 2024
f532d0b
use secrets map
vhvb1989 Jan 23, 2024
2b07c61
tests
vhvb1989 Jan 23, 2024
dcbf8fd
remove regex
vhvb1989 Jan 23, 2024
289d519
rem extra space
vhvb1989 Jan 23, 2024
a0149e9
nest resource
vhvb1989 Jan 23, 2024
8f05daa
snap
vhvb1989 Jan 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 7 additions & 6 deletions cli/azd/.vscode/cspell-azd-dictionary.txt
Expand Up @@ -19,8 +19,9 @@ armapimanagement
armappconfiguration
armappplatform
armcognitiveservices
asyncmy
armcosmos
armresourcegraph
asyncmy
asyncpg
azapi
AZCLI
Expand Down Expand Up @@ -52,8 +53,8 @@ buildpacks
byoi
cflags
circleci
cmdsubst
cmdrecord
cmdsubst
cognitiveservices
consolesize
containerapp
Expand Down Expand Up @@ -97,8 +98,8 @@ hotspot
iidfile
ineffassign
javac
jquery
jmes
jquery
keychain
LASTEXITCODE
ldflags
Expand All @@ -114,8 +115,8 @@ mockarmresources
mockazcli
mongojs
mvnw
mysqldb
mysqlclient
mysqldb
nobanner
nodeapp
nolint
Expand Down Expand Up @@ -148,10 +149,10 @@ reauthentication
relogin
remarshal
repourl
requirepass
resourcegraph
restoreapp
retriable
requirepass
rzip
secureobject
securestring
Expand Down Expand Up @@ -189,4 +190,4 @@ vuejs
westus2
wireinject
yacspin
zerr
zerr
8 changes: 8 additions & 0 deletions cli/azd/internal/scaffold/funcs.go
Expand Up @@ -149,6 +149,14 @@ func ContainerAppName(name string) string {
return containerAppName(name, containerAppNameMaxLen)
}

// ContainerAppSecretName returns a suitable name a container app secret name.
//
// The name is treated to only contain lowercase alphanumeric and dash characters, and must start and end with an
// alphanumeric character
func ContainerAppSecretName(name string) string {
return strings.ReplaceAll(strings.ToLower(name), "_", "-")
}

// ContainerAppInfix returns a suitable infix for a container app resource.
//
// The name is treated to only contain alphanumeric and dash characters, with no repeated dashes, and no dashes
Expand Down
51 changes: 37 additions & 14 deletions cli/azd/pkg/apphost/generate.go
Expand Up @@ -23,6 +23,7 @@ const RedisContainerAppService = "redis"

const DaprStateStoreComponentType = "state"
const DaprPubSubComponentType = "pubsub"
const containerAppSecretConnectionString = "{{ connectionString "
hemarina marked this conversation as resolved.
Show resolved Hide resolved

// genTemplates is the collection of templates that are used when generating infrastructure files from a manifest.
var genTemplates *template.Template
Expand All @@ -32,9 +33,10 @@ func init() {
Option("missingkey=error").
Funcs(
template.FuncMap{
"bicepName": scaffold.BicepName,
"alphaSnakeUpper": scaffold.AlphaSnakeUpper,
"containerAppName": scaffold.ContainerAppName,
"bicepName": scaffold.BicepName,
"alphaSnakeUpper": scaffold.AlphaSnakeUpper,
"containerAppName": scaffold.ContainerAppName,
"containerAppSecretName": scaffold.ContainerAppSecretName,
},
).
ParseFS(resources.AppHostTemplates, "apphost/templates/*")
Expand Down Expand Up @@ -338,6 +340,8 @@ func (b *infraGenerator) LoadManifest(m *Manifest) error {
b.addStorageTable(*comp.Parent, name)
case "azure.cosmosdb.account.v0":
b.addCosmosDbAccount(name)
case "azure.cosmosdb.database.v0":
b.addCosmosDatabase(*comp.Parent, name)
case "postgres.server.v0":
// We currently use a ACA Postgres Service per database. Because of this, we don't need to retain any
// information from the server resource.
Expand Down Expand Up @@ -411,7 +415,15 @@ func (b *infraGenerator) addAppInsights(name string) {
}

func (b *infraGenerator) addCosmosDbAccount(name string) {
b.bicepContext.CosmosDbAccounts[name] = genCosmosAccount{}
if _, exists := b.bicepContext.CosmosDbAccounts[name]; !exists {
b.bicepContext.CosmosDbAccounts[name] = genCosmosAccount{}
}
}

func (b *infraGenerator) addCosmosDatabase(cosmosDbAccount, dbName string) {
account := b.bicepContext.CosmosDbAccounts[cosmosDbAccount]
account.Databases = append(account.Databases, dbName)
b.bicepContext.CosmosDbAccounts[cosmosDbAccount] = account
}

func (b *infraGenerator) addProject(
Expand Down Expand Up @@ -663,8 +675,9 @@ func (b *infraGenerator) Compile() error {

for resourceName, docker := range b.dockerfiles {
projectTemplateCtx := genContainerAppManifestTemplateContext{
Name: resourceName,
Env: make(map[string]string),
Name: resourceName,
Env: make(map[string]string),
Secrets: make(map[string]string),
}

ingress, err := buildIngress(docker.Bindings)
Expand All @@ -683,8 +696,9 @@ func (b *infraGenerator) Compile() error {

for resourceName, project := range b.projects {
projectTemplateCtx := genContainerAppManifestTemplateContext{
Name: resourceName,
Env: make(map[string]string),
Name: resourceName,
Env: make(map[string]string),
Secrets: make(map[string]string),
}

binding, err := validateAndMergeBindings(project.Bindings)
Expand Down Expand Up @@ -868,10 +882,14 @@ func (b infraGenerator) evalBindingRef(v string, emitType inputEmitType) (string
return "",
fmt.Errorf("malformed binding expression, expected bindings.<binding-name>.[host|port|url] but was: %s", v)
}
case targetType == "postgres.database.v0" || targetType == "redis.v0":
case targetType == "postgres.database.v0" ||
targetType == "redis.v0" ||
targetType == "azure.cosmosdb.account.v0" ||
targetType == "azure.cosmosdb.database.v0":
switch prop {
case "connectionString":
return fmt.Sprintf(`{{ connectionString "%s" }}`, resource), nil
// returns something like {{ connectionString "resource" }}
return fmt.Sprintf(`%s"%s" }}`, containerAppSecretConnectionString, resource), nil
default:
return "", errUnsupportedProperty(targetType, prop)
}
Expand Down Expand Up @@ -902,8 +920,7 @@ func (b infraGenerator) evalBindingRef(v string, emitType inputEmitType) (string
case targetType == "azure.keyvault.v0" ||
targetType == "azure.storage.blob.v0" ||
targetType == "azure.storage.queue.v0" ||
targetType == "azure.storage.table.v0" ||
targetType == "azure.cosmosdb.account.v0":
targetType == "azure.storage.table.v0":
switch prop {
case "connectionString":
return fmt.Sprintf("{{ .Env.SERVICE_BINDING_%s_ENDPOINT }}", scaffold.AlphaSnakeUpper(resource)), nil
Expand Down Expand Up @@ -953,8 +970,14 @@ func (b *infraGenerator) buildEnvBlock(env map[string]string, manifestCtx *genCo

// remove the trailing newline. yaml marshall will add a newline at the end of the string, as the new line is
// expected at the end of the yaml document. But we are getting a single value with valid yaml here, so we don't
// need the newline.
manifestCtx.Env[k] = string(yamlString[0 : len(yamlString)-1])
// need the newline
value := string(yamlString[0 : len(yamlString)-1])

if strings.Contains(value, containerAppSecretConnectionString) {
manifestCtx.Secrets[k] = value
} else {
manifestCtx.Env[k] = value
}
}

return nil
Expand Down
7 changes: 6 additions & 1 deletion cli/azd/pkg/apphost/generate_test.go
Expand Up @@ -206,17 +206,22 @@ func TestBuildEnvResolveServiceToConnectionString(t *testing.T) {
expected := map[string]string{
"VAR1": "value1",
"VAR2": "value2",
}

expectedSecrets := map[string]string{
"VAR3": `complex {{ connectionString "service" }} expression`,
}

manifestCtx := &genContainerAppManifestTemplateContext{
Env: make(map[string]string),
Env: make(map[string]string),
Secrets: make(map[string]string),
}

// Call the method being tested
err := mockGenerator.buildEnvBlock(env, manifestCtx)
require.NoError(t, err)
require.Equal(t, expected, manifestCtx.Env)
require.Equal(t, expectedSecrets, manifestCtx.Secrets)
}

func TestAddContainerAppService(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/pkg/apphost/generate_types.go
Expand Up @@ -9,6 +9,7 @@ type genStorageAccount struct {
}

type genCosmosAccount struct {
Databases []string
}

type genServiceBus struct {
Expand Down Expand Up @@ -106,6 +107,7 @@ type genContainerAppManifestTemplateContext struct {
Name string
Ingress *genContainerAppIngress
Env map[string]string
Secrets map[string]string
Dapr *genContainerAppManifestTemplateContextDapr
}

Expand Down
Expand Up @@ -73,7 +73,6 @@ resource mysqlabstract 'Microsoft.App/containerApps@2023-05-02-preview' = {
tags: union(tags, {'aspire-resource-name': 'mysqlabstract'})
}


output MANAGED_IDENTITY_CLIENT_ID string = managedIdentity.properties.clientId
output AZURE_CONTAINER_APPS_ENVIRONMENT_ID string = containerAppEnvironment.id
output AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = containerAppEnvironment.properties.defaultDomain
Expand Down
Expand Up @@ -95,7 +95,6 @@ resource mysqlabstract 'Microsoft.App/containerApps@2023-05-02-preview' = {
tags: union(tags, {'aspire-resource-name': 'mysqlabstract'})
}


output MANAGED_IDENTITY_CLIENT_ID string = managedIdentity.properties.clientId
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerRegistry.properties.loginServer
output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = managedIdentity.id
Expand Down
7 changes: 5 additions & 2 deletions cli/azd/pkg/apphost/testdata/TestAspireEscaping-api.snap
Expand Up @@ -16,19 +16,22 @@ properties:
registries:
- server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}
identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}
secrets:
- name: connectionstrings--db
value: '{{ connectionString "db" }}'
template:
containers:
- image: {{ .Image }}
name: api
env:
- name: AZURE_CLIENT_ID
value: {{ .Env.MANAGED_IDENTITY_CLIENT_ID }}
- name: ConnectionStrings__db
value: '{{ connectionString "db" }}'
- name: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES
value: "true"
- name: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES
value: "true"
- name: ConnectionStrings__db
secretRef: connectionstrings--db
scale:
minReplicas: 1
tags:
Expand Down
Expand Up @@ -130,7 +130,6 @@ resource photosQueuesRoleAssignment 'Microsoft.Authorization/roleAssignments@202
}
}


output MANAGED_IDENTITY_CLIENT_ID string = managedIdentity.properties.clientId
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerRegistry.properties.loginServer
output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = managedIdentity.id
Expand Down
8 changes: 8 additions & 0 deletions cli/azd/pkg/azd/default.go
Expand Up @@ -5,7 +5,9 @@ import (
"fmt"
"strings"

"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/azure/azure-dev/cli/azd/pkg/azsdk/storage"
"github.com/azure/azure-dev/cli/azd/pkg/cosmosdb"
"github.com/azure/azure-dev/cli/azd/pkg/environment"
"github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning"
infraBicep "github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning/bicep"
Expand Down Expand Up @@ -109,6 +111,12 @@ func (p *DefaultPlatform) ConfigureContainer(container *ioc.NestedContainer) err
container.RegisterSingleton(storage.NewBlobClient)
container.RegisterSingleton(storage.NewBlobSdkClient)

// cosmosdb
container.RegisterSingleton(func() *arm.ClientOptions {
return &arm.ClientOptions{}
})
container.RegisterSingleton(cosmosdb.NewCosmosDbService)

// Templates

// Gets a list of default template sources used in azd.
Expand Down
2 changes: 1 addition & 1 deletion cli/azd/pkg/azsdk/zip_deploy_client_test.go
Expand Up @@ -98,7 +98,7 @@ func registerDeployMocks(mockContext *mocks.MockContext) {
return request.Method == http.MethodPost && strings.Contains(request.URL.Path, "/api/zipdeploy")
}).RespondFn(func(request *http.Request) (*http.Response, error) {
response, _ := mocks.CreateEmptyHttpResponse(request, http.StatusAccepted)
response.Header.Set("Location", "http://myapp.scm.azurewebsites.net/deployments/latest")
response.Header.Set("Location", "https://myapp.scm.azurewebsites.net/deployments/latest")

return response, nil
})
Expand Down
55 changes: 55 additions & 0 deletions cli/azd/pkg/cosmosdb/cosmosdb.go
@@ -0,0 +1,55 @@
package cosmosdb

import (
"context"
"fmt"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmos/armcosmos/v2"
)

// CosmosDbService is the interface for the CosmosDbService
type CosmosDbService interface {
ConnectionString(ctx context.Context, subId, rgName, accountName string) (string, error)
}

type cosmosDbClient struct {
credential azcore.TokenCredential
options *arm.ClientOptions
}

// NewCosmosDbService creates a new instance of the CosmosDbService
func NewCosmosDbService(
credential azcore.TokenCredential,
options *arm.ClientOptions,
) (CosmosDbService, error) {
return &cosmosDbClient{
credential: credential,
options: options,
}, nil
}

// ConnectionString returns the connection string for the CosmosDB account
func (c *cosmosDbClient) ConnectionString(ctx context.Context, subId, rgName, accountName string) (string, error) {
client, err := armcosmos.NewDatabaseAccountsClient(subId, c.credential, c.options)
if err != nil {
return "", err
}
res, err := client.ListConnectionStrings(
ctx, rgName, accountName, &armcosmos.DatabaseAccountsClientListConnectionStringsOptions{})

if err != nil {
return "", err
}

if res.ConnectionStrings == nil {
return "", fmt.Errorf("connection strings are nil")
}

if len(res.ConnectionStrings) == 0 {
return "", fmt.Errorf("no connection strings found")
}

return *res.ConnectionStrings[0].ConnectionString, nil
}