-
-
Notifications
You must be signed in to change notification settings - Fork 586
feat(azure): add cosmosdb module #3452
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
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
3dae4aa
feat: add cosmosdb module
natsoman 52f0365
fix: typo
natsoman 5950cb8
refactor: move cosmosdb package to azure module
natsoman 0a6b6e2
chore: remove references to cosmosdb module
natsoman c165e20
docs: update docs/modules/azure.md
natsoman f931f2f
Merge branch 'main' into cosmosdb-module
natsoman 6307960
feat: improve readiness check with port listening
natsoman a7142be
docs: improve ContainerPolicy description
natsoman 9b7a941
fix: wrap error in NewContainerPolicy
natsoman 8023b20
docs: document hardcoded key
natsoman e44e880
chore(deps): bump azcore version to "v1.19.1"
natsoman 50983a6
fix: unnecessary nested wait.ForAll
natsoman 1be2ace
docs: update test account key comment
natsoman 2791023
fix: .vscode/.testcontainers-go.code-workspace extra line
natsoman 2aaf95d
chore: doc ConnectionString and move const to top
natsoman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| package cosmosdb | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| "github.com/docker/go-connections/nat" | ||
|
|
||
| "github.com/testcontainers/testcontainers-go" | ||
| "github.com/testcontainers/testcontainers-go/wait" | ||
| ) | ||
|
|
||
| const ( | ||
| defaultPort = "8081/tcp" | ||
| defaultProtocol = "http" | ||
|
|
||
| // Well-known, publicly documented account key for the Azure CosmosDB Emulator. | ||
| // See: https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-develop-emulator | ||
| testAccKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" | ||
| ) | ||
|
|
||
| // Container represents the CosmosDB container type used in the module | ||
| type Container struct { | ||
| testcontainers.Container | ||
| } | ||
|
|
||
| // Run creates an instance of the CosmosDB container type | ||
| func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) { | ||
| // Initialize with module defaults | ||
| moduleOpts := []testcontainers.ContainerCustomizer{ | ||
| testcontainers.WithExposedPorts(defaultPort), | ||
| testcontainers.WithCmdArgs("--enable-explorer", "false"), | ||
| testcontainers.WithWaitStrategy( | ||
| wait.ForAll( | ||
| wait.ForLog("Started"), | ||
| wait.ForListeningPort(nat.Port(defaultPort)), | ||
| ), | ||
| ), | ||
| } | ||
|
|
||
| // Add user-provided options | ||
| moduleOpts = append(moduleOpts, opts...) | ||
|
|
||
| ctr, err := testcontainers.Run(ctx, img, moduleOpts...) | ||
| var c *Container | ||
| if ctr != nil { | ||
| c = &Container{Container: ctr} | ||
| } | ||
|
|
||
| if err != nil { | ||
| return c, fmt.Errorf("run cosmosdb: %w", err) | ||
| } | ||
|
|
||
| return c, nil | ||
| } | ||
|
|
||
| // ConnectionString returns a connection string that can be used to connect to the CosmosDB emulator. | ||
| // The connection string includes the account endpoint (host:port) and the default test account key. | ||
| // It returns an error if the port endpoint cannot be determined. | ||
| // | ||
| // Format: "AccountEndpoint=<host>:<port>;AccountKey=<accountKey>" | ||
| func (c *Container) ConnectionString(ctx context.Context) (string, error) { | ||
| endpoint, err := c.PortEndpoint(ctx, defaultPort, defaultProtocol) | ||
| if err != nil { | ||
| return "", fmt.Errorf("port endpoint: %w", err) | ||
| } | ||
|
|
||
| return fmt.Sprintf("AccountEndpoint=%s;AccountKey=%s;", endpoint, testAccKey), nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| package cosmosdb_test | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "testing" | ||
|
|
||
| "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" | ||
| "github.com/stretchr/testify/require" | ||
|
|
||
| "github.com/testcontainers/testcontainers-go" | ||
| "github.com/testcontainers/testcontainers-go/modules/azure/cosmosdb" | ||
| ) | ||
|
|
||
| func TestCosmosDB(t *testing.T) { | ||
| ctx := context.Background() | ||
|
|
||
| ctr, err := cosmosdb.Run(ctx, "mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview") | ||
| testcontainers.CleanupContainer(t, ctr) | ||
| require.NoError(t, err) | ||
|
|
||
| // Create Azure Cosmos client | ||
| connStr, err := ctr.ConnectionString(ctx) | ||
| require.NoError(t, err) | ||
| require.NotNil(t, connStr) | ||
|
|
||
| p, err := cosmosdb.NewContainerPolicy(ctx, ctr) | ||
| require.NoError(t, err) | ||
|
|
||
| client, err := azcosmos.NewClientFromConnectionString(connStr, p.ClientOptions()) | ||
| require.NoError(t, err) | ||
| require.NotNil(t, client) | ||
|
|
||
| // Create database | ||
| createDatabaseResp, err := client.CreateDatabase(ctx, azcosmos.DatabaseProperties{ID: "myDatabase"}, nil) | ||
| require.NoError(t, err) | ||
| require.NotNil(t, createDatabaseResp) | ||
|
|
||
| dbClient, err := client.NewDatabase("myDatabase") | ||
| require.NoError(t, err) | ||
| require.NotNil(t, dbClient) | ||
|
|
||
| // Create container | ||
| containerProps := azcosmos.ContainerProperties{ | ||
| ID: "myContainer", | ||
| PartitionKeyDefinition: azcosmos.PartitionKeyDefinition{Paths: []string{"/category"}}, | ||
| } | ||
| createContainerResp, err := dbClient.CreateContainer(ctx, containerProps, nil) | ||
| require.NoError(t, err) | ||
| require.NotNil(t, createContainerResp) | ||
| containerClient, err := dbClient.NewContainer("myContainer") | ||
| require.NoError(t, err) | ||
| require.NotNil(t, containerClient) | ||
|
|
||
| // Create item | ||
| type Product struct { | ||
| ID string `json:"id"` | ||
| Category string `json:"category"` | ||
| Name string `json:"name"` | ||
| } | ||
|
|
||
| testItem := Product{ID: "item123", Category: "gear-surf-surfboards", Name: "Yamba Surfboard"} | ||
|
|
||
| pk := azcosmos.NewPartitionKeyString(testItem.Category) | ||
|
|
||
| jsonItem, err := json.Marshal(testItem) | ||
| require.NoError(t, err) | ||
|
|
||
| createItemResp, err := containerClient.CreateItem(ctx, pk, jsonItem, nil) | ||
| require.NoError(t, err) | ||
| require.NotNil(t, createItemResp) | ||
|
|
||
| // Read item | ||
| readItemResp, err := containerClient.ReadItem(ctx, pk, testItem.ID, nil) | ||
| require.NoError(t, err) | ||
| require.NotNil(t, readItemResp) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| package cosmosdb_test | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "log" | ||
| "net/http" | ||
|
|
||
| "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" | ||
|
|
||
| "github.com/testcontainers/testcontainers-go" | ||
| "github.com/testcontainers/testcontainers-go/modules/azure/cosmosdb" | ||
| ) | ||
|
|
||
| func ExampleRun() { | ||
| ctx := context.Background() | ||
|
|
||
| cosmosdbContainer, err := cosmosdb.Run(ctx, "mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview") | ||
| defer func() { | ||
| if err := testcontainers.TerminateContainer(cosmosdbContainer); err != nil { | ||
| log.Printf("failed to terminate container: %s", err) | ||
| } | ||
| }() | ||
| if err != nil { | ||
| log.Printf("failed to start container: %s", err) | ||
| return | ||
| } | ||
| // } | ||
|
|
||
| state, err := cosmosdbContainer.State(ctx) | ||
| if err != nil { | ||
| log.Printf("failed to get container state: %s", err) | ||
| return | ||
| } | ||
|
|
||
| fmt.Println(state.Running) | ||
|
|
||
| // Output: | ||
| // true | ||
| } | ||
|
|
||
| func ExampleRun_connect() { | ||
| ctx := context.Background() | ||
|
|
||
| cosmosdbContainer, err := cosmosdb.Run(ctx, "mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview") | ||
| defer func() { | ||
| if err := testcontainers.TerminateContainer(cosmosdbContainer); err != nil { | ||
| log.Printf("failed to terminate container: %s", err) | ||
| } | ||
| }() | ||
| if err != nil { | ||
| log.Printf("failed to start container: %s", err) | ||
| return | ||
| } | ||
|
|
||
| connString, err := cosmosdbContainer.ConnectionString(ctx) | ||
| if err != nil { | ||
| log.Printf("failed to get connection string: %s", err) | ||
| return | ||
| } | ||
|
|
||
| p, err := cosmosdb.NewContainerPolicy(ctx, cosmosdbContainer) | ||
| if err != nil { | ||
| log.Printf("failed to create policy: %s", err) | ||
| return | ||
| } | ||
|
|
||
| client, err := azcosmos.NewClientFromConnectionString(connString, p.ClientOptions()) | ||
| if err != nil { | ||
| log.Printf("failed to create cosmosdb client: %s", err) | ||
| return | ||
| } | ||
|
|
||
| createDatabaseResp, err := client.CreateDatabase(ctx, azcosmos.DatabaseProperties{ID: "myDatabase"}, nil) | ||
| if err != nil { | ||
| log.Printf("failed to create database: %s", err) | ||
| return | ||
| } | ||
| // } | ||
|
|
||
| fmt.Println(createDatabaseResp.RawResponse.StatusCode == http.StatusCreated) | ||
|
|
||
| // Output: | ||
| // true | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package cosmosdb | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "net/http" | ||
|
|
||
| "github.com/Azure/azure-sdk-for-go/sdk/azcore" | ||
| "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" | ||
| "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" | ||
| ) | ||
|
|
||
| // ContainerPolicy ensures that requests always target the CosmosDB emulator container endpoint. | ||
| // It overrides the CosmosDB client's globalEndpointManager, which would otherwise dynamically | ||
| // update [http.Request.Host] based on global endpoint discovery, pinning all requests to the container. | ||
| type ContainerPolicy struct { | ||
| endpoint string | ||
| } | ||
|
|
||
| func NewContainerPolicy(ctx context.Context, c *Container) (*ContainerPolicy, error) { | ||
| endpoint, err := c.PortEndpoint(ctx, defaultPort, "") | ||
| if err != nil { | ||
| return nil, fmt.Errorf("port endpoint: %w", err) | ||
| } | ||
|
|
||
| return &ContainerPolicy{ | ||
| endpoint: endpoint, | ||
| }, nil | ||
| } | ||
|
|
||
| func (p *ContainerPolicy) Do(req *policy.Request) (*http.Response, error) { | ||
| req.Raw().Host = p.endpoint | ||
| req.Raw().URL.Host = p.endpoint | ||
|
|
||
| return req.Next() | ||
| } | ||
|
|
||
| // ClientOptions returns Azure CosmosDB client options that contain ContainerPolicy. | ||
| func (p *ContainerPolicy) ClientOptions() *azcosmos.ClientOptions { | ||
| return &azcosmos.ClientOptions{ | ||
| ClientOptions: azcore.ClientOptions{ | ||
| PerRetryPolicies: []policy.Policy{p}, | ||
| }, | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.